diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index aae8d6c351c..36ec33b5962 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -111,16 +111,17 @@ public class FCGI public static final String AUTH_TYPE = "AUTH_TYPE"; public static final String CONTENT_LENGTH = "CONTENT_LENGTH"; public static final String CONTENT_TYPE = "CONTENT_TYPE"; + public static final String DOCUMENT_ROOT = "DOCUMENT_ROOT"; public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; public static final String PATH_INFO = "PATH_INFO"; - public static final String PATH_TRANSLATED = "PATH_TRANSLATED"; public static final String QUERY_STRING = "QUERY_STRING"; public static final String REMOTE_ADDR = "REMOTE_ADDR"; - public static final String REMOTE_HOST = "REMOTE_HOST"; - public static final String REMOTE_USER = "REMOTE_USER"; + public static final String REMOTE_PORT = "REMOTE_PORT"; public static final String REQUEST_METHOD = "REQUEST_METHOD"; public static final String REQUEST_URI = "REQUEST_URI"; + public static final String SCRIPT_FILENAME = "SCRIPT_FILENAME"; public static final String SCRIPT_NAME = "SCRIPT_NAME"; + public static final String SERVER_ADDR = "SERVER_ADDR"; public static final String SERVER_NAME = "SERVER_NAME"; public static final String SERVER_PORT = "SERVER_PORT"; public static final String SERVER_PROTOCOL = "SERVER_PROTOCOL"; diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index d8b6640a362..d9c0973a4ed 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -18,9 +18,11 @@ package org.eclipse.jetty.fcgi.parser; +import java.nio.ByteBuffer; import java.util.EnumMap; import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpField; public class ClientParser extends Parser { @@ -28,9 +30,11 @@ public class ClientParser extends Parser public ClientParser(Listener listener) { - contentParsers.put(FCGI.FrameType.STDOUT, new ResponseContentParser(headerParser, listener)); - contentParsers.put(FCGI.FrameType.STDERR, new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener)); - contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, listener)); + ResponseContentParser stdOutParser = new ResponseContentParser(headerParser, listener); + contentParsers.put(FCGI.FrameType.STDOUT, stdOutParser); + StreamContentParser stdErrParser = new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener); + contentParsers.put(FCGI.FrameType.STDERR, stdErrParser); + contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, new EndRequestListener(listener, stdOutParser, stdErrParser))); } @Override @@ -51,4 +55,48 @@ public class ClientParser extends Parser } } } + + private class EndRequestListener implements Listener + { + private final Listener listener; + private final StreamContentParser[] streamParsers; + + private EndRequestListener(Listener listener, StreamContentParser... streamParsers) + { + this.listener = listener; + this.streamParsers = streamParsers; + } + + @Override + public void onBegin(int request, int code, String reason) + { + listener.onBegin(request, code, reason); + } + + @Override + public void onHeader(int request, HttpField field) + { + listener.onHeader(request, field); + } + + @Override + public void onHeaders(int request) + { + listener.onHeaders(request); + } + + @Override + public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + { + listener.onContent(request, stream, buffer); + } + + @Override + public void onEnd(int request) + { + listener.onEnd(request); + for (StreamContentParser streamParser : streamParsers) + streamParser.end(request); + } + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 5a72638bfd5..30e2427a3b9 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -19,11 +19,13 @@ package org.eclipse.jetty.fcgi.parser; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; @@ -34,46 +36,89 @@ public class ResponseContentParser extends StreamContentParser { private static final Logger LOG = Log.getLogger(ResponseContentParser.class); + private final Map parsers = new ConcurrentHashMap<>(); + private final ClientParser.Listener listener; + public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener) { - super(headerParser, FCGI.StreamType.STD_OUT, new ResponseListener(headerParser, listener)); + super(headerParser, FCGI.StreamType.STD_OUT, listener); + this.listener = listener; } - private static class ResponseListener extends Parser.Listener.Adapter implements HttpParser.ResponseHandler + @Override + protected void onContent(ByteBuffer buffer) { - private final HeaderParser headerParser; - private final ClientParser.Listener listener; + int request = getRequest(); + ResponseParser parser = parsers.get(request); + if (parser == null) + { + parser = new ResponseParser(listener, request); + parsers.put(request, parser); + } + parser.parse(buffer); + } + + @Override + protected void end(int request) + { + super.end(request); + parsers.remove(request); + } + + private class ResponseParser implements HttpParser.ResponseHandler + { + private final HttpFields fields = new HttpFields(); + private ClientParser.Listener listener; + private final int request; private final FCGIHttpParser httpParser; private State state = State.HEADERS; - private boolean begun; - private List fields; + private boolean seenResponseCode; - public ResponseListener(HeaderParser headerParser, ClientParser.Listener listener) + private ResponseParser(ClientParser.Listener listener, int request) { - this.headerParser = headerParser; this.listener = listener; + this.request = request; this.httpParser = new FCGIHttpParser(this); } - @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public void parse(ByteBuffer buffer) { - LOG.debug("Request {} {} content {} {}", request, stream, state, buffer); + LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer); - while (buffer.hasRemaining()) + int remaining = buffer.remaining(); + while (remaining > 0) { switch (state) { case HEADERS: { if (httpParser.parseHeaders(buffer)) - state = State.CONTENT; + state = State.CONTENT_MODE; + remaining = buffer.remaining(); break; } - case CONTENT: + case CONTENT_MODE: { - if (httpParser.parseContent(buffer)) - reset(); + // If we have no indication of the content, then + // the HTTP parser will assume there is no content + // and will not parse it even if it is provided, + // so we have to parse it raw ourselves here. + boolean rawContent = fields.size() == 0 || + (fields.get(HttpHeader.CONTENT_LENGTH) == null && + fields.get(HttpHeader.TRANSFER_ENCODING) == null); + state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT; + break; + } + case RAW_CONTENT: + { + notifyContent(buffer); + remaining = 0; + break; + } + case HTTP_CONTENT: + { + httpParser.parseContent(buffer); + remaining = buffer.remaining(); break; } default: @@ -84,22 +129,6 @@ public class ResponseContentParser extends StreamContentParser } } - private void reset() - { - httpParser.reset(); - state = State.HEADERS; - begun = false; - fields = null; - } - - @Override - public void onEnd(int request) - { - // We are a STD_OUT stream so the end of the request is - // signaled by a END_REQUEST. Here we just reset the state. - reset(); - } - @Override public int getHeaderCacheSize() { @@ -119,41 +148,30 @@ public class ResponseContentParser extends StreamContentParser { try { - if ("Status".equalsIgnoreCase(httpField.getName())) + String name = httpField.getName(); + if ("Status".equalsIgnoreCase(name)) { - if (!begun) + if (!seenResponseCode) { - begun = true; + seenResponseCode = true; // Need to set the response status so the // HttpParser can handle the content properly. String[] parts = httpField.getValue().split(" "); int code = Integer.parseInt(parts[0]); httpParser.setResponseStatus(code); - String reason = parts.length > 1 ? parts[1] : HttpStatus.getMessage(code); - listener.onBegin(headerParser.getRequest(), code, reason); - if (fields != null) - { - for (HttpField field : fields) - listener.onHeader(headerParser.getRequest(), field); - fields = null; - } + notifyBegin(code, reason); + notifyHeaders(fields); } } else { - if (begun) - { - listener.onHeader(headerParser.getRequest(), httpField); - } + if (seenResponseCode) + notifyHeader(httpField); else - { - if (fields == null) - fields = new ArrayList<>(); fields.add(httpField); - } } } catch (Throwable x) @@ -162,48 +180,89 @@ public class ResponseContentParser extends StreamContentParser } return false; } - - @Override - public boolean headerComplete() + private void notifyBegin(int code, String reason) { try { - if (begun) - { - listener.onHeaders(headerParser.getRequest()); - } - else - { - fields = null; - // TODO: what here ? - } + listener.onBegin(request, code, reason); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); } + } + + private void notifyHeader(HttpField httpField) + { + try + { + listener.onHeader(request, httpField); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + private void notifyHeaders(HttpFields fields) + { + if (fields != null) + { + for (HttpField field : fields) + notifyHeader(field); + } + } + + private void notifyHeaders() + { + try + { + listener.onHeaders(request); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } + } + + @Override + public boolean headerComplete() + { + if (!seenResponseCode) + { + // No Status header but we have other headers, assume 200 OK + notifyBegin(200, "OK"); + notifyHeaders(fields); + } + notifyHeaders(); // Return from parsing so that we can parse the content return true; } @Override public boolean content(ByteBuffer buffer) + { + notifyContent(buffer); + return false; + } + + private void notifyContent(ByteBuffer buffer) { try { - listener.onContent(headerParser.getRequest(), FCGI.StreamType.STD_OUT, buffer); + listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); } - return false; } @Override public boolean messageComplete() { - // Return from parsing so that we can parse the next headers + // Return from parsing so that we can parse the next headers or the raw content. + // No need to notify the listener because it will be done by FCGI_END_REQUEST. return true; } @@ -218,45 +277,45 @@ public class ResponseContentParser extends StreamContentParser { // TODO } + } - // Methods overridden to make them visible here - private static class FCGIHttpParser extends HttpParser + // Methods overridden to make them visible here + private static class FCGIHttpParser extends HttpParser + { + private FCGIHttpParser(ResponseHandler handler) { - private FCGIHttpParser(ResponseHandler handler) - { - super(handler, 65 * 1024, true); - reset(); - } - - @Override - public void reset() - { - super.reset(); - setState(State.HEADER); - } - - @Override - protected boolean parseHeaders(ByteBuffer buffer) - { - return super.parseHeaders(buffer); - } - - @Override - protected boolean parseContent(ByteBuffer buffer) - { - return super.parseContent(buffer); - } - - @Override - protected void setResponseStatus(int status) - { - super.setResponseStatus(status); - } + super(handler, 65 * 1024, true); + reset(); } - private enum State + @Override + public void reset() { - HEADERS, CONTENT + super.reset(); + setState(State.HEADER); + } + + @Override + protected boolean parseHeaders(ByteBuffer buffer) + { + return super.parseHeaders(buffer); + } + + @Override + protected boolean parseContent(ByteBuffer buffer) + { + return super.parseContent(buffer); + } + + @Override + protected void setResponseStatus(int status) + { + super.setResponseStatus(status); } } + + private enum State + { + HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT + } } diff --git a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java index 10c15be663e..950cd59f747 100644 --- a/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java +++ b/fcgi-core/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java @@ -80,7 +80,14 @@ public class StreamContentParser extends ContentParser @Override public void noContent() { - onEnd(); + try + { + listener.onEnd(getRequest()); + } + catch (Throwable x) + { + logger.debug("Exception while invoking listener " + listener, x); + } } protected void onContent(ByteBuffer buffer) @@ -95,16 +102,8 @@ public class StreamContentParser extends ContentParser } } - protected void onEnd() + protected void end(int request) { - try - { - listener.onEnd(getRequest()); - } - catch (Throwable x) - { - logger.debug("Exception while invoking listener " + listener, x); - } } private enum State diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index 7349500ecc2..157893d5a6b 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -154,21 +154,21 @@ public class ClientGeneratorTest final int id = 13; Generator.Result result = generator.generateRequestContent(id, content, true, null); - final AtomicInteger length = new AtomicInteger(); + final AtomicInteger totalLength = new AtomicInteger(); ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); - length.addAndGet(buffer.remaining()); + totalLength.addAndGet(buffer.remaining()); } @Override public void onEnd(int request) { Assert.assertEquals(id, request); - Assert.assertEquals(contentLength, length.get()); + Assert.assertEquals(contentLength, totalLength.get()); } }); diff --git a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index d5a43eee184..bd2f5abf763 100644 --- a/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/fcgi-core/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -214,7 +214,7 @@ public class ClientParserTest Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); Generator.Result result2 = generator.generateResponseContent(id, content, true, null); - final AtomicInteger length = new AtomicInteger(); + final AtomicInteger totalLength = new AtomicInteger(); final AtomicBoolean verifier = new AtomicBoolean(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @@ -222,14 +222,14 @@ public class ClientParserTest public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); - length.addAndGet(buffer.remaining()); + totalLength.addAndGet(buffer.remaining()); } @Override public void onEnd(int request) { Assert.assertEquals(id, request); - Assert.assertEquals(contentLength, length.get()); + Assert.assertEquals(contentLength, totalLength.get()); verifier.set(true); } }); diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index ad095fe17e7..6146d0ff85f 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -111,7 +111,7 @@ public class HttpChannelOverFCGI extends HttpChannel receiver.responseSuccess(exchange); } - protected void flush(Generator.Result result) + protected void flush(Generator.Result... result) { flusher.flush(result); } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index cbbbee4c506..6f2b1aca812 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -18,50 +18,56 @@ package org.eclipse.jetty.fcgi.client.http; -import java.util.regex.Pattern; +import java.io.IOException; +import java.util.Map; import org.eclipse.jetty.client.AbstractHttpClientTransport; import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.Promise; // TODO: add parameter to tell whether use multiplex destinations or not public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport { - private final Pattern uriPattern; + private final String scriptRoot; - public HttpClientTransportOverFCGI(Pattern uriPattern) + public HttpClientTransportOverFCGI(String scriptRoot) { - this(1, uriPattern); + this(Runtime.getRuntime().availableProcessors() / 2 + 1, scriptRoot); } - public HttpClientTransportOverFCGI(int selectors, Pattern uriPattern) + public HttpClientTransportOverFCGI(int selectors, String scriptRoot) { super(selectors); - this.uriPattern = uriPattern; - } - - public Pattern getURIPattern() - { - return uriPattern; + this.scriptRoot = scriptRoot; } @Override - public HttpDestination newHttpDestination(String scheme, String host, int port) + public HttpDestination newHttpDestination(Origin origin) { - return new MultiplexHttpDestinationOverFCGI(getHttpClient(), scheme, host, port); + return new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin); } @Override - protected org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, HttpDestination destination) + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException { - return new HttpConnectionOverFCGI(endPoint, destination); + HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); + HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination); + @SuppressWarnings("unchecked") + Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); + promise.succeeded(connection); + return connection; } - @Override - public Connection tunnel(Connection connection) + protected void customize(Request request, HttpFields fastCGIHeaders) { - HttpConnectionOverFCGI httpConnection = (HttpConnectionOverFCGI)connection; - return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), connection); + String scriptName = fastCGIHeaders.get(FCGI.Headers.SCRIPT_NAME); + fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, scriptRoot + scriptName); + fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, scriptRoot); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 3e8fce96a29..81dd8f4b9ba 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -1,8 +1,7 @@ package org.eclipse.jetty.fcgi.client.http; import java.net.URI; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Locale; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpContent; @@ -16,6 +15,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Jetty; public class HttpSenderOverFCGI extends HttpSender { @@ -36,59 +36,66 @@ public class HttpSenderOverFCGI extends HttpSender @Override protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) { - Request httpRequest = exchange.getRequest(); - URI uri = httpRequest.getURI(); - HttpFields headers = httpRequest.getHeaders(); + Request request = exchange.getRequest(); + // Copy the request headers to be able to convert them properly + HttpFields headers = new HttpFields(); + for (HttpField field : request.getHeaders()) + headers.put(field); + HttpFields fcgiHeaders = new HttpFields(); - HttpField field = headers.remove(HttpHeader.AUTHORIZATION); - if (field != null) - headers.put(FCGI.Headers.AUTH_TYPE, field.getValue()); + // FastCGI headers based on the URI + URI uri = request.getURI(); + String path = uri.getPath(); + fcgiHeaders.put(FCGI.Headers.REQUEST_URI, path); + String query = uri.getQuery(); + fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query); + int lastSegment = path.lastIndexOf('/'); + String scriptName = lastSegment < 0 ? path : path.substring(lastSegment); + fcgiHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName); - field = headers.remove(HttpHeader.CONTENT_LENGTH); - if (field != null) - headers.put(FCGI.Headers.CONTENT_LENGTH, field.getValue()); + // FastCGI headers based on HTTP headers + HttpField httpField = headers.remove(HttpHeader.AUTHORIZATION); + if (httpField != null) + fcgiHeaders.put(FCGI.Headers.AUTH_TYPE, httpField.getValue()); + httpField = headers.remove(HttpHeader.CONTENT_LENGTH); + fcgiHeaders.put(FCGI.Headers.CONTENT_LENGTH, httpField == null ? "" : httpField.getValue()); + httpField = headers.remove(HttpHeader.CONTENT_TYPE); + fcgiHeaders.put(FCGI.Headers.CONTENT_TYPE, httpField == null ? "" : httpField.getValue()); - field = headers.remove(HttpHeader.CONTENT_TYPE); - if (field != null) - headers.put(FCGI.Headers.CONTENT_TYPE, field.getValue()); + // FastCGI headers that are not based on HTTP headers nor URI + fcgiHeaders.put(FCGI.Headers.REQUEST_METHOD, request.getMethod()); + fcgiHeaders.put(FCGI.Headers.SERVER_PROTOCOL, request.getVersion().asString()); + fcgiHeaders.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); + fcgiHeaders.put(FCGI.Headers.SERVER_SOFTWARE, "Jetty/" + Jetty.VERSION); + // TODO: need to pass SERVER_NAME, SERVER_ADDR, SERVER_PORT, REMOTE_ADDR, REMOTE_PORT + // TODO: if the FCGI transport is used within a ProxyServlet, they must have certain values + // TODO: for example the remote address is taken from the ProxyServlet request + // TODO: if used standalone, they must have other values. - headers.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1"); - - HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport(); - Pattern uriPattern = transport.getURIPattern(); - Matcher matcher = uriPattern.matcher(uri.toString()); - - // TODO: what if the URI does not match ? Here is kinda too late to abort the request ? - // TODO: perhaps this works in conjuntion with the ProxyServlet, which is mapped to the same URI regexp - // TODO: so that if the call arrives here, we are sure it matches. - -// headers.put(Headers.PATH_INFO, ???); -// headers.put(Headers.PATH_TRANSLATED, ???); - - headers.put(FCGI.Headers.QUERY_STRING, uri.getQuery()); - - // TODO: the fields below are probably provided by ProxyServlet as X-Forwarded-* -// headers.put(Headers.REMOTE_ADDR, ???); -// headers.put(Headers.REMOTE_HOST, ???); -// headers.put(Headers.REMOTE_USER, ???); - - headers.put(FCGI.Headers.REQUEST_METHOD, httpRequest.getMethod()); - - headers.put(FCGI.Headers.REQUEST_URI, uri.toString()); - - headers.put(FCGI.Headers.SERVER_PROTOCOL, httpRequest.getVersion().asString()); - - // TODO: translate remaining HTTP header into the HTTP_* format - - int request = getHttpChannel().getRequest(); - boolean hasContent = content.hasContent(); - Generator.Result result = generator.generateRequestHeaders(request, headers, - hasContent ? callback : new Callback.Adapter()); - getHttpChannel().flush(result); - if (!hasContent) + // Translate remaining HTTP header into the HTTP_* format + for (HttpField field : headers) { - result = generator.generateRequestContent(request, null, true, callback); - getHttpChannel().flush(result); + String name = field.getName(); + String fcgiName = "HTTP_" + name.replaceAll("-", "_").toUpperCase(Locale.ENGLISH); + fcgiHeaders.add(fcgiName, field.getValue()); + } + + // Give a chance to the transport implementation to customize the FastCGI headers + HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport(); + transport.customize(request, fcgiHeaders); + + int id = getHttpChannel().getRequest(); + boolean hasContent = content.hasContent(); + Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, + hasContent ? callback : new Callback.Adapter()); + if (hasContent) + { + getHttpChannel().flush(headersResult); + } + else + { + Generator.Result noContentResult = generator.generateRequestContent(id, null, true, callback); + getHttpChannel().flush(headersResult, noContentResult); } } diff --git a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java index 171b06c5e43..3a823c4992b 100644 --- a/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java +++ b/fcgi-http-client-transport/src/main/java/org/eclipse/jetty/fcgi/client/http/MultiplexHttpDestinationOverFCGI.java @@ -21,12 +21,13 @@ package org.eclipse.jetty.fcgi.client.http; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.MultiplexHttpDestination; +import org.eclipse.jetty.client.Origin; public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination { - public MultiplexHttpDestinationOverFCGI(HttpClient client, String scheme, String host, int port) + public MultiplexHttpDestinationOverFCGI(HttpClient client, Origin origin) { - super(client, scheme, host, port); + super(client, origin); } @Override diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java index c58ebcf7c87..7dc0e497bda 100644 --- a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/AbstractHttpClientServerTest.java @@ -55,7 +55,7 @@ public abstract class AbstractHttpClientServerTest QueuedThreadPool executor = new QueuedThreadPool(); executor.setName(executor.getName() + "-client"); - client = new HttpClient(new HttpClientTransportOverFCGI(), null); + client = new HttpClient(new HttpClientTransportOverFCGI(""), null); client.setExecutor(executor); client.start(); } diff --git a/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java new file mode 100644 index 00000000000..496134e71a5 --- /dev/null +++ b/fcgi-http-client-transport/src/test/java/org/eclipse/jetty/fcgi/client/http/ExternalFastCGIServerTest.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.fcgi.client.http; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +public class ExternalFastCGIServerTest +{ + @Test + @Ignore("Relies on an external server") + public void testExternalFastCGIServer() throws Exception + { + // Assume a FastCGI server is listening on port 9000 + + HttpClient client = new HttpClient(new HttpClientTransportOverFCGI("/var/www/php-fcgi"), null); + client.start(); + + ContentResponse response = client.newRequest("localhost", 9000) + .path("/index.php") + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(200, response.getStatus()); + + Files.write(Paths.get(System.getProperty("java.io.tmpdir"), "fcgi_response.html"), response.getContent(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + } +}