From a7c91ae38aee21e2cb4043db3edc67f0a5d72f7b Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 11 Feb 2013 18:34:20 +0400 Subject: [PATCH] http request handlers should not be netty (third-party dependency) channel handlers xml rpc server serve now only "/" and "/RPC2" (case-insensitive) urls --- .../src/com/intellij/ide/XmlRpcServer.java | 5 +- .../org/jetbrains/ide/HttpRequestHandler.java | 32 ++++++ .../org/jetbrains/ide/WebServerManager.java | 20 +++- .../com/intellij/ide/XmlRpcServerImpl.java | 93 ++++++++-------- .../jetbrains/ide/WebServerManagerImpl.java | 18 +--- .../src/org/jetbrains/io/Responses.java | 21 +++- .../src/org/jetbrains/io/WebServer.java | 100 +++++++++++------- .../src/META-INF/PlatformExtensionPoints.xml | 4 +- .../src/META-INF/PlatformExtensions.xml | 2 +- 9 files changed, 175 insertions(+), 120 deletions(-) create mode 100644 platform/platform-api/src/org/jetbrains/ide/HttpRequestHandler.java diff --git a/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java b/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java index 61caaf83d7d2..445268d4270b 100644 --- a/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java +++ b/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,6 @@ package com.intellij.ide; import com.intellij.openapi.components.ServiceManager; -/** - * @author mike - */ public interface XmlRpcServer { void addHandler(String name, Object handler); boolean hasHandler(String name); diff --git a/platform/platform-api/src/org/jetbrains/ide/HttpRequestHandler.java b/platform/platform-api/src/org/jetbrains/ide/HttpRequestHandler.java new file mode 100644 index 000000000000..e1e8d78a3587 --- /dev/null +++ b/platform/platform-api/src/org/jetbrains/ide/HttpRequestHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed 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.jetbrains.ide; + +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.QueryStringDecoder; + +import java.io.IOException; + +public abstract class HttpRequestHandler { + public boolean isSupported(HttpMethod method) { + return method == HttpMethod.GET; + } + + public abstract boolean process(QueryStringDecoder urlDecoder, HttpRequest request, ChannelHandlerContext context) + throws IOException; +} diff --git a/platform/platform-api/src/org/jetbrains/ide/WebServerManager.java b/platform/platform-api/src/org/jetbrains/ide/WebServerManager.java index 4d2ae225d83d..bab944f17f1f 100644 --- a/platform/platform-api/src/org/jetbrains/ide/WebServerManager.java +++ b/platform/platform-api/src/org/jetbrains/ide/WebServerManager.java @@ -1,15 +1,27 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed 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.jetbrains.ide; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.util.Consumer; import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.channel.ChannelPipeline; public abstract class WebServerManager { // Your handler will be instantiated on first user request - public static final ExtensionPointName> EP_NAME = - ExtensionPointName.create("com.intellij.serverPipelineConsumer"); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.httpRequestHandler"); public static WebServerManager getInstance() { return ServiceManager.getService(WebServerManager.class); diff --git a/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java b/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java index 900495084ca8..dbf50cf1ee76 100644 --- a/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,24 +17,25 @@ package com.intellij.ide; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; -import com.intellij.util.Consumer; import gnu.trove.THashMap; import org.apache.xmlrpc.*; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBufferInputStream; import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.*; +import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.QueryStringDecoder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.ide.HttpRequestHandler; import org.jetbrains.io.Responses; +import java.io.IOException; import java.util.Arrays; -@ChannelHandler.Sharable -public class XmlRpcServerImpl extends SimpleChannelUpstreamHandler implements XmlRpcServer { +public class XmlRpcServerImpl implements XmlRpcServer { private static final Logger LOG = Logger.getInstance(XmlRpcServerImpl.class); private final XmlRpcHandlerMappingImpl handlerMapping; @@ -76,11 +77,15 @@ public class XmlRpcServerImpl extends SimpleChannelUpstreamHandler implements Xm LOG.debug("XmlRpcServerImpl instantiated, handlers " + handlerMapping); } - static final class XmlRpcPipelineConsumer implements Consumer { + static final class XmlRpcRequestHandler extends HttpRequestHandler { @Override - public void consume(ChannelPipeline pipeline) { - XmlRpcServer xmlRpcServer = SERVICE.getInstance(); - pipeline.addLast("pluggable_xmlRpc", (XmlRpcServerImpl)xmlRpcServer); + public boolean isSupported(HttpMethod method) { + return method == HttpMethod.POST || method == HttpMethod.OPTIONS; + } + + @Override + public boolean process(QueryStringDecoder urlDecoder, HttpRequest request, ChannelHandlerContext context) throws IOException { + return ((XmlRpcServerImpl)SERVICE.getInstance()).process(urlDecoder, request, context); } } @@ -97,52 +102,40 @@ public class XmlRpcServerImpl extends SimpleChannelUpstreamHandler implements Xm handlerMapping.removeHandler(name); } - public void messageReceived(ChannelHandlerContext context, MessageEvent e) throws Exception { - if (e.getMessage() instanceof HttpRequest) { - HttpRequest request = (HttpRequest)e.getMessage(); + private boolean process(QueryStringDecoder urlDecoder, HttpRequest request, ChannelHandlerContext context) throws IOException { + if (!(urlDecoder.getPath().isEmpty() || urlDecoder.getPath().equalsIgnoreCase("/RPC2"))) { + return false; + } - if (request.getMethod() == HttpMethod.POST) { - ChannelBuffer result; - ChannelBufferInputStream in = new ChannelBufferInputStream(request.getContent()); - try { - result = ChannelBuffers.copiedBuffer(new XmlRpcWorker(handlerMapping).execute(in, xmlRpcContext)); - } - catch (Throwable ex) { - context.getChannel().close(); - LOG.error(ex); - return; - } - finally { - in.close(); - } - - HttpResponse response = Responses.create("text/xml"); - response.setContent(result); - Responses.send(response, request, context); - return; + if (request.getMethod() == HttpMethod.POST) { + ChannelBuffer result; + ChannelBufferInputStream in = new ChannelBufferInputStream(request.getContent()); + try { + result = ChannelBuffers.copiedBuffer(new XmlRpcWorker(handlerMapping).execute(in, xmlRpcContext)); + } + catch (Throwable ex) { + context.getChannel().close(); + LOG.error(ex); + return true; + } + finally { + in.close(); } - if (request.getMethod() == HttpMethod.OPTIONS && - HttpMethod.POST.getName().equals(request.getHeader("Access-Control-Request-Method"))) { - HttpResponse response = Responses.create("text/plain"); - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); - Responses.send(response, request, context); - return; - } + HttpResponse response = Responses.create("text/xml"); + response.setContent(result); + Responses.send(response, request, context); + return true; } - - context.sendUpstream(e); - } - - @Override - public void exceptionCaught(ChannelHandlerContext context, ExceptionEvent e) throws Exception { - try { - LOG.error(e.getCause()); - } - finally { - e.getChannel().close(); + else if (HttpMethod.POST.getName().equals(request.getHeader("Access-Control-Request-Method"))) { + assert request.getMethod() == HttpMethod.OPTIONS; + HttpResponse response = Responses.create("text/plain"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); + Responses.send(response, request, context); + return true; } + return false; } private static class XmlRpcHandlerMappingImpl implements XmlRpcHandlerMapping { diff --git a/platform/platform-impl/src/org/jetbrains/ide/WebServerManagerImpl.java b/platform/platform-impl/src/org/jetbrains/ide/WebServerManagerImpl.java index 84e485f381c7..3f1fed82a75b 100644 --- a/platform/platform-impl/src/org/jetbrains/ide/WebServerManagerImpl.java +++ b/platform/platform-impl/src/org/jetbrains/ide/WebServerManagerImpl.java @@ -9,16 +9,11 @@ import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.SimpleTimer; -import com.intellij.util.Consumer; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.channel.ChannelPipeline; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import org.jetbrains.io.WebServer; @@ -114,18 +109,7 @@ class WebServerManagerImpl extends WebServerManager implements Disposable { return; } - detectedPortNumber = server.start(getDefaultPort(), PORTS_COUNT, true, new Computable[]>() { - @Override - public Consumer[] compute() { - Consumer[] consumers = Extensions.getExtensions(EP_NAME); - if (consumers.length == 0) { - LOG.warn("web server will be stopped, there are no pipeline consumers"); - SimpleTimer.getInstance().setUp(server.createShutdownTask(), 3000); - } - return consumers; - } - }); - + detectedPortNumber = server.start(getDefaultPort(), PORTS_COUNT, true); if (detectedPortNumber == -1) { LOG.info("web server cannot be started, cannot bind to port"); } diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.java b/platform/platform-impl/src/org/jetbrains/io/Responses.java index 81052c00a04f..dcb340f3ad62 100644 --- a/platform/platform-impl/src/org/jetbrains/io/Responses.java +++ b/platform/platform-impl/src/org/jetbrains/io/Responses.java @@ -1,9 +1,25 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed 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.jetbrains.io; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationInfoEx; import org.jboss.netty.buffer.BigEndianHeapChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; @@ -74,7 +90,10 @@ public final class Responses { } public static void send(HttpResponse response, HttpRequest request, ChannelHandlerContext context) { - setContentLength(response, response.getContent().readableBytes()); + ChannelBuffer content = response.getContent(); + if (content != ChannelBuffers.EMPTY_BUFFER) { + setContentLength(response, content.readableBytes()); + } send(response, context, !isKeepAlive(request)); } diff --git a/platform/platform-impl/src/org/jetbrains/io/WebServer.java b/platform/platform-impl/src/org/jetbrains/io/WebServer.java index 534ee7c0ae51..8b76c6416e9c 100644 --- a/platform/platform-impl/src/org/jetbrains/io/WebServer.java +++ b/platform/platform-impl/src/org/jetbrains/io/WebServer.java @@ -17,10 +17,8 @@ package org.jetbrains.io; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.Consumer; import com.intellij.util.concurrency.Semaphore; import com.intellij.util.containers.ContainerUtil; import org.jboss.netty.bootstrap.ClientBootstrap; @@ -35,7 +33,9 @@ import org.jboss.netty.handler.codec.http.*; import org.jboss.netty.util.CharsetUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.ide.HttpRequestHandler; import org.jetbrains.ide.PooledThreadExecutor; +import org.jetbrains.ide.WebServerManager; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -71,26 +71,18 @@ public class WebServer { return !openChannels.isEmpty(); } - public void start(int port, Consumer... pipelineConsumers) { - start(port, new Computable.PredefinedValueComputable[]>(pipelineConsumers)); + public void start(int port) { + start(port, 1, false); } - public void start(int port, int portsCount, Consumer... pipelineConsumers) { - start(port, portsCount, false, new Computable.PredefinedValueComputable[]>(pipelineConsumers)); - } - - public void start(int port, Computable[]> pipelineConsumers) { - start(port, 1, false, pipelineConsumers); - } - - public int start(int firstPort, int portsCount, boolean tryAnyPort, Computable[]> pipelineConsumers) { + public int start(int firstPort, int portsCount, boolean tryAnyPort) { if (isRunning()) { throw new IllegalStateException("server already started"); } ServerBootstrap bootstrap = new ServerBootstrap(channelFactory); bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setPipelineFactory(new ChannelPipelineFactoryImpl(pipelineConsumers, new DefaultHandler(openChannels))); + bootstrap.setPipelineFactory(new ChannelPipelineFactoryImpl(new DefaultHandler(openChannels))); return bind(firstPort, portsCount, tryAnyPort, bootstrap); } @@ -307,28 +299,20 @@ public class WebServer { } } + public static void replaceDefaultHandler(@NotNull ChannelHandlerContext context, @NotNull SimpleChannelUpstreamHandler messageChannelHandler) { + context.getPipeline().replace(DefaultHandler.class, "replacedDefaultHandler", messageChannelHandler); + } + private static class ChannelPipelineFactoryImpl implements ChannelPipelineFactory { - private final Computable[]> pipelineConsumers; private final DefaultHandler defaultHandler; - public ChannelPipelineFactoryImpl(Computable[]> pipelineConsumers, DefaultHandler defaultHandler) { - this.pipelineConsumers = pipelineConsumers; + public ChannelPipelineFactoryImpl(DefaultHandler defaultHandler) { this.defaultHandler = defaultHandler; } @Override public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(new HttpRequestDecoder(), new HttpChunkAggregator(1048576), new HttpResponseEncoder()); - for (Consumer consumer : pipelineConsumers.compute()) { - try { - consumer.consume(pipeline); - } - catch (Throwable e) { - LOG.error(e); - } - } - pipeline.addLast("defaultHandler", defaultHandler); - return pipeline; + return pipeline(new HttpRequestDecoder(), new HttpChunkAggregator(1048576), new HttpResponseEncoder(), defaultHandler); } } @@ -346,21 +330,55 @@ public class WebServer { } @Override - public void messageReceived(ChannelHandlerContext context, MessageEvent e) throws Exception { - if (e.getMessage() instanceof HttpRequest) { - HttpRequest message = (HttpRequest)e.getMessage(); - if (new QueryStringDecoder(message.getUri()).getPath().equals(START_TIME_PATH)) { - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); - response.setHeader("Access-Control-Allow-Origin", "*"); - response.setContent(ChannelBuffers.copiedBuffer(getApplicationStartTime(), CharsetUtil.US_ASCII)); - Responses.addServer(response); - Responses.addDate(response); - context.getChannel().write(response).addListener(ChannelFutureListener.CLOSE); - } - else { - Responses.sendError(message, context, NOT_FOUND); + public void messageReceived(ChannelHandlerContext context, MessageEvent event) throws Exception { + if (!(event.getMessage() instanceof HttpRequest)) { + context.sendUpstream(event); + } + + HttpRequest request = (HttpRequest)event.getMessage(); + QueryStringDecoder urlDecoder = new QueryStringDecoder(request.getUri()); + if (urlDecoder.getPath().equals(START_TIME_PATH)) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setContent(ChannelBuffers.copiedBuffer(getApplicationStartTime(), CharsetUtil.US_ASCII)); + Responses.addServer(response); + Responses.addDate(response); + Responses.send(response, context); + return; + } + + HttpRequestHandler connectedHandler = (HttpRequestHandler)context.getAttachment(); + if (connectedHandler == null) { + for (HttpRequestHandler handler : WebServerManager.EP_NAME.getExtensions()) { + try { + if (handler.isSupported(request.getMethod()) && handler.process(urlDecoder, request, context)) { + if (context.getAttachment() == null) { + context.setAttachment(handler); + } + return; + } + } + catch (Throwable e) { + LOG.error(e); + } } } + else if (connectedHandler.isSupported(request.getMethod())) { + connectedHandler.process(urlDecoder, request, context); + return; + } + Responses.sendError(request, context, NOT_FOUND); + } + + @Override + public void exceptionCaught(ChannelHandlerContext context, ExceptionEvent event) throws Exception { + try { + LOG.error(event.getCause()); + } + finally { + context.setAttachment(null); + event.getChannel().close(); + } } } } diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index 0ab2c373a8b5..3926f1a73f93 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -149,7 +149,7 @@ - - + + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 6a055bc54557..ad4b9242c132 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -289,7 +289,7 @@ - +