diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java index 01514c96f594..2a8f6eade5e3 100644 --- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java +++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java @@ -24,9 +24,9 @@ import io.netty.handler.codec.http.QueryStringDecoder; abstract class DelegatingHttpRequestHandlerBase extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext context, FullHttpRequest message) throws Exception { - //if (BuiltInServer.LOG.isDebugEnabled()) { - //BuiltInServer.LOG.debug(request.toString()); - //} + if (BuiltInServer.LOG.isDebugEnabled()) { + BuiltInServer.LOG.debug("IN HTTP:\n" + message); + } if (!process(context, message, new QueryStringDecoder(message.getUri()))) { Responses.sendStatus(HttpResponseStatus.NOT_FOUND, context.channel(), message); diff --git a/platform/platform-impl/src/org/jetbrains/io/FileResponses.java b/platform/platform-impl/src/org/jetbrains/io/FileResponses.java index 3cebd0cf9f0a..c33c8cc58012 100644 --- a/platform/platform-impl/src/org/jetbrains/io/FileResponses.java +++ b/platform/platform-impl/src/org/jetbrains/io/FileResponses.java @@ -32,6 +32,7 @@ import java.io.RandomAccessFile; import java.text.ParseException; import java.util.Date; +import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static org.jetbrains.io.Responses.*; public class FileResponses { @@ -46,7 +47,7 @@ public class FileResponses { if (!StringUtil.isEmpty(ifModifiedSince)) { try { if (Responses.DATE_FORMAT.get().parse(ifModifiedSince).getTime() >= lastModified) { - sendStatus(HttpResponseStatus.NOT_MODIFIED, channel, request); + send(response(HttpResponseStatus.NOT_MODIFIED), channel, request); return true; } } @@ -67,7 +68,8 @@ public class FileResponses { RandomAccessFile raf = new RandomAccessFile(file, "r"); try { long fileLength = raf.length(); - HttpResponse response = create(getContentType(file.getPath())); + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + response.headers().add(CONTENT_TYPE, getContentType(file.getPath())); addCommonHeaders(response); response.headers().set(HttpHeaders.Names.CACHE_CONTROL, "private, must-revalidate"); response.headers().set(HttpHeaders.Names.LAST_MODIFIED, Responses.DATE_FORMAT.get().format(new Date(file.lastModified()))); @@ -76,22 +78,22 @@ public class FileResponses { HttpHeaders.setContentLength(response, fileLength); } - ChannelFuture future = channel.write(response); + channel.write(response); if (request.getMethod() != HttpMethod.HEAD) { if (channel.pipeline().get(SslHandler.class) == null) { // No encryption - use zero-copy - future = channel.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength)); + channel.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength)); } else { // Cannot use zero-copy with HTTPS - future = channel.write(new ChunkedFile(raf, 0, fileLength, 8192)); + channel.write(new ChunkedFile(raf, 0, fileLength, 8192)); } } + ChannelFuture future = channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); if (!keepAlive) { future.addListener(ChannelFutureListener.CLOSE); } - channel.flush(); fileWillBeClosed = true; } diff --git a/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java b/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java index 429bc6844cd4..116976350bb9 100644 --- a/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java +++ b/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java @@ -17,12 +17,11 @@ package org.jetbrains.io; import com.intellij.openapi.util.AtomicNotNullLazyValue; import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPipeline; +import io.netty.channel.*; import io.netty.handler.codec.compression.JZlibEncoder; import io.netty.handler.codec.compression.JdkZlibDecoder; import io.netty.handler.codec.compression.ZlibWrapper; +import io.netty.handler.codec.http.HttpMessage; import io.netty.handler.ssl.SslHandler; import io.netty.handler.stream.ChunkedWriteHandler; import org.jetbrains.annotations.NotNull; @@ -105,6 +104,17 @@ final class PortUnificationServerHandler extends Decoder { else { NettyUtil.initHttpHandlers(pipeline); pipeline.addLast(delegatingHttpRequestHandler); + if (BuiltInServer.LOG.isDebugEnabled()) { + pipeline.addLast(new ChannelOutboundHandlerAdapter() { + @Override + public void write(ChannelHandlerContext context, Object message, ChannelPromise promise) throws Exception { + if (message instanceof HttpMessage) { + BuiltInServer.LOG.debug("OUT HTTP:\n" + message); + } + super.write(context, message, promise); + } + }); + } } } // must be after new channels handlers addition (netty bug?) diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.java b/platform/platform-impl/src/org/jetbrains/io/Responses.java index 9ad056ff5b06..dccd49d198a1 100644 --- a/platform/platform-impl/src/org/jetbrains/io/Responses.java +++ b/platform/platform-impl/src/org/jetbrains/io/Responses.java @@ -52,6 +52,19 @@ public final class Responses { private static String SERVER_HEADER_VALUE; + public static FullHttpResponse response(HttpResponseStatus status) { + return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.EMPTY_BUFFER); + } + + public static HttpResponse create(@Nullable String contentType, @Nullable ByteBuf content) { + HttpResponse response = + new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content == null ? Unpooled.EMPTY_BUFFER : content); + if (contentType != null) { + response.headers().add(CONTENT_TYPE, contentType); + } + return response; + } + public static void addAllowAnyOrigin(HttpResponse response) { response.headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); } @@ -88,18 +101,10 @@ public final class Responses { } } - public static void send(String contentType, CharSequence content, Channel channel, HttpRequest request) { - send(create(contentType, Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)), channel, request); - } - public static void send(HttpResponse response, Channel channel, @Nullable HttpRequest request) { - if (response.getStatus() != HttpResponseStatus.NOT_MODIFIED) { - if (response instanceof FullHttpResponse) { - HttpHeaders.setContentLength(response, ((FullHttpResponse)response).content().readableBytes()); - } - else if (!HttpHeaders.isContentLengthSet(response)) { - HttpHeaders.setContentLength(response, 0); - } + if (response.getStatus() != HttpResponseStatus.NOT_MODIFIED && !HttpHeaders.isContentLengthSet(response)) { + HttpHeaders.setContentLength(response, + response instanceof FullHttpResponse ? ((FullHttpResponse)response).content().readableBytes() : 0); } addCommonHeaders(response); @@ -120,34 +125,12 @@ public final class Responses { addAllowAnyOrigin(response); } - public static HttpResponse create(String contentType) { - return create(contentType, null); - } - - public static HttpResponse create(@Nullable String contentType, @Nullable ByteBuf content) { - HttpResponse response = content == null - ? new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK) - : new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); - if (contentType != null) { - response.headers().add(CONTENT_TYPE, contentType); - } - return response; - } - public static void send(CharSequence content, Channel channel, @Nullable HttpRequest request) { send(content, CharsetUtil.US_ASCII, channel, request); } public static void send(CharSequence content, Charset charset, Channel channel, @Nullable HttpRequest request) { - send(create(null, Unpooled.copiedBuffer(content, charset)), channel, request); - } - - public static void send(HttpResponse response, Channel channel) { - send(response, channel, true); - } - - public static void send(HttpResponseStatus status, ChannelHandlerContext context) { - send(new DefaultHttpResponse(HttpVersion.HTTP_1_1, status), context.channel()); + send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(content, charset)), channel, request); } private static void send(HttpResponse response, Channel channel, boolean close) { @@ -155,7 +138,11 @@ public final class Responses { return; } - ChannelFuture future = channel.writeAndFlush(response); + ChannelFuture future = channel.write(response); + if (!(response instanceof FullHttpResponse)) { + channel.write(LastHttpContent.EMPTY_LAST_CONTENT); + } + channel.flush(); if (close) { future.addListener(ChannelFutureListener.CLOSE); } @@ -173,14 +160,9 @@ public final class Responses { send(createStatusResponse(responseStatus, request, description), channel, request); } - public static void sendStatus(HttpResponse response, HttpRequest request, Channel channel) { - response.headers().add(CONTENT_TYPE, "text/html"); - send(response, channel, request); - } - private static HttpResponse createStatusResponse(HttpResponseStatus responseStatus, @Nullable HttpRequest request, @Nullable String description) { if (request != null && request.getMethod() == HttpMethod.HEAD) { - return new DefaultHttpResponse(HttpVersion.HTTP_1_1, responseStatus); + return response(responseStatus); } StringBuilder builder = new StringBuilder(); @@ -197,8 +179,7 @@ public final class Responses { } public static void sendOptionsResponse(String allowHeaders, HttpRequest request, ChannelHandlerContext context) { - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - addAllowAnyOrigin(response); + HttpResponse response = response(HttpResponseStatus.OK); response.headers().set(ACCESS_CONTROL_ALLOW_METHODS, allowHeaders); response.headers().set(ALLOW, allowHeaders); send(response, context.channel(), request);