Skip to content

Commit

Permalink
Refactor sendfile with compression support in mind. Use an approach s…
Browse files Browse the repository at this point in the history
…imilar to HTTP/1.1 so the StreamProcessor will have visibility of whether sendfile is in use when deciding whether or not to compress.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1816571 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
markt-asf committed Nov 28, 2017
1 parent 0cd22d3 commit 6ca79c8
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 38 deletions.
45 changes: 10 additions & 35 deletions java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
Expand Up @@ -16,10 +16,8 @@
*/ */
package org.apache.coyote.http2; package org.apache.coyote.http2;


import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.CompletionHandler; import java.nio.channels.CompletionHandler;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileChannel.MapMode;
Expand Down Expand Up @@ -264,47 +262,37 @@ protected void processWrites() throws IOException {
} }


@Override @Override
protected SendfileState processSendfile(Stream stream) { protected SendfileState processSendfile(SendfileData sendfile) {
String fileName = (String) stream.getCoyoteRequest().getAttribute( if (sendfile != null) {
org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
if (fileName != null) {
java.nio.file.Path path = new File(fileName).toPath();
SendfileData sendfile = new SendfileData();
sendfile.pos = ((Long) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILE_START_ATTR)).longValue();
sendfile.end = ((Long) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
sendfile.left = sendfile.end - sendfile.pos;
try { try {
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) { try (FileChannel channel = FileChannel.open(sendfile.path, StandardOpenOption.READ)) {
sendfile.mappedBuffer = channel.map(MapMode.READ_ONLY, sendfile.pos, sendfile.end - sendfile.pos); sendfile.mappedBuffer = channel.map(MapMode.READ_ONLY, sendfile.pos, sendfile.end - sendfile.pos);
sendfile.stream = stream;
} }
// Reserve as much as possible right away // Reserve as much as possible right away
int reservation = (sendfile.end - sendfile.pos > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (sendfile.end - sendfile.pos); int reservation = (sendfile.end - sendfile.pos > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) (sendfile.end - sendfile.pos);
sendfile.streamReservation = stream.reserveWindowSize(reservation, true); sendfile.streamReservation = sendfile.stream.reserveWindowSize(reservation, true);
sendfile.connectionReservation = reserveWindowSize(stream, sendfile.streamReservation); sendfile.connectionReservation = reserveWindowSize(sendfile.stream, sendfile.streamReservation);
} catch (IOException e) { } catch (IOException e) {
return SendfileState.ERROR; return SendfileState.ERROR;
} }
// Actually perform the write // Actually perform the write
int frameSize = Integer.min(getMaxFrameSize(), sendfile.connectionReservation); int frameSize = Integer.min(getMaxFrameSize(), sendfile.connectionReservation);
boolean finished = (frameSize == sendfile.left) && stream.getCoyoteResponse().getTrailerFields() == null; boolean finished = (frameSize == sendfile.left) && sendfile.stream.getCoyoteResponse().getTrailerFields() == null;


// Need to check this now since sending end of stream will change this. // Need to check this now since sending end of stream will change this.
boolean writeable = stream.canWrite(); boolean writeable = sendfile.stream.canWrite();
byte[] header = new byte[9]; byte[] header = new byte[9];
ByteUtil.setThreeBytes(header, 0, frameSize); ByteUtil.setThreeBytes(header, 0, frameSize);
header[3] = FrameType.DATA.getIdByte(); header[3] = FrameType.DATA.getIdByte();
if (finished) { if (finished) {
header[4] = FLAG_END_OF_STREAM; header[4] = FLAG_END_OF_STREAM;
stream.sentEndOfStream(); sendfile.stream.sentEndOfStream();
if (!stream.isActive()) { if (!sendfile.stream.isActive()) {
activeRemoteStreamCount.decrementAndGet(); activeRemoteStreamCount.decrementAndGet();
} }
} }
if (writeable) { if (writeable) {
ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue()); ByteUtil.set31Bits(header, 5, sendfile.stream.getIdentifier().intValue());
sendfile.mappedBuffer.limit(sendfile.mappedBuffer.position() + frameSize); sendfile.mappedBuffer.limit(sendfile.mappedBuffer.position() + frameSize);
socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(), socketWrapper.write(BlockingMode.SEMI_BLOCK, protocol.getWriteTimeout(),
TimeUnit.MILLISECONDS, sendfile, COMPLETE_WRITE_WITH_COMPLETION, TimeUnit.MILLISECONDS, sendfile, COMPLETE_WRITE_WITH_COMPLETION,
Expand Down Expand Up @@ -397,19 +385,6 @@ public void failed(Throwable t, SendfileData sendfile) {
} }
} }


protected class SendfileData {
protected Stream stream;
// Note: a mapped buffer is a special construct with an underlying file
// that doesn't need to be closed
protected MappedByteBuffer mappedBuffer;
protected int frameSize;
protected long left;
protected int streamReservation;
protected int connectionReservation;
protected long pos;
protected long end;
}

protected class AsyncPingManager extends PingManager { protected class AsyncPingManager extends PingManager {
@Override @Override
public void sendPing(boolean force) throws IOException { public void sendPing(boolean force) throws IOException {
Expand Down
5 changes: 3 additions & 2 deletions java/org/apache/coyote/http2/Http2UpgradeHandler.java
Expand Up @@ -808,14 +808,15 @@ protected void incrementWindowSize(int increment) throws Http2Exception {
* Process send file (if supported) for the given stream. The appropriate * Process send file (if supported) for the given stream. The appropriate
* request attributes should be set before calling this method. * request attributes should be set before calling this method.
* *
* @param stream The stream to process * @param sendfileData The stream and associated data to process
* *
* @return The result of the send file processing * @return The result of the send file processing
*/ */
protected SendfileState processSendfile(Stream stream) { protected SendfileState processSendfile(SendfileData sendfileData) {
return SendfileState.DONE; return SendfileState.DONE;
} }



private synchronized Set<AbstractStream> releaseBackLog(int increment) { private synchronized Set<AbstractStream> releaseBackLog(int increment) {
Set<AbstractStream> result = new HashSet<>(); Set<AbstractStream> result = new HashSet<>();
if (backLogSize < increment) { if (backLogSize < increment) {
Expand Down
34 changes: 34 additions & 0 deletions java/org/apache/coyote/http2/SendfileData.java
@@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.coyote.http2;

import java.nio.MappedByteBuffer;
import java.nio.file.Path;

class SendfileData {
Path path;
Stream stream;
// Note: a mapped buffer is a special construct with an underlying file
// that doesn't need to be closed
MappedByteBuffer mappedBuffer;
int frameSize;
long left;
int streamReservation;
int connectionReservation;
long pos;
long end;
}
23 changes: 22 additions & 1 deletion java/org/apache/coyote/http2/StreamProcessor.java
Expand Up @@ -16,6 +16,7 @@
*/ */
package org.apache.coyote.http2; package org.apache.coyote.http2;


import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;


Expand Down Expand Up @@ -45,6 +46,7 @@ class StreamProcessor extends AbstractProcessor {


private final Http2UpgradeHandler handler; private final Http2UpgradeHandler handler;
private final Stream stream; private final Stream stream;
private SendfileData sendfileData = null;
private SendfileState sendfileState = null; private SendfileState sendfileState = null;




Expand Down Expand Up @@ -101,11 +103,30 @@ final void process(SocketEvent event) {
@Override @Override
protected final void prepareResponse() throws IOException { protected final void prepareResponse() throws IOException {
response.setCommitted(true); response.setCommitted(true);
if (handler.hasAsyncIO() && handler.getProtocol().getUseSendfile()) {
prepareSendfile();
}
prepareHeaders(response); prepareHeaders(response);
stream.writeHeaders(); stream.writeHeaders();
} }




private void prepareSendfile() {
String fileName = (String) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
if (fileName != null) {
sendfileData.path = new File(fileName).toPath();
sendfileData = new SendfileData();
sendfileData.pos = ((Long) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILE_START_ATTR)).longValue();
sendfileData.end = ((Long) stream.getCoyoteRequest().getAttribute(
org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
sendfileData.left = sendfileData.end - sendfileData.pos;
sendfileData.stream = stream;
}
}


static void prepareHeaders(Response coyoteResponse) { static void prepareHeaders(Response coyoteResponse) {
MimeHeaders headers = coyoteResponse.getMimeHeaders(); MimeHeaders headers = coyoteResponse.getMimeHeaders();
int statusCode = coyoteResponse.getStatus(); int statusCode = coyoteResponse.getStatus();
Expand Down Expand Up @@ -135,7 +156,7 @@ static void prepareHeaders(Response coyoteResponse) {


@Override @Override
protected final void finishResponse() throws IOException { protected final void finishResponse() throws IOException {
sendfileState = handler.processSendfile(stream); sendfileState = handler.processSendfile(sendfileData);
if (!(sendfileState == SendfileState.PENDING)) { if (!(sendfileState == SendfileState.PENDING)) {
stream.getOutputBuffer().close(); stream.getOutputBuffer().close();
} }
Expand Down

0 comments on commit 6ca79c8

Please sign in to comment.