Implemented response content handling.
Now the implementation can successfully make requests to PHP-FPM and receive the responses.
This commit is contained in:
parent
67a287e5cf
commit
642f8c1ee1
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Integer, ResponseParser> 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<ByteBuffer>
|
||||
@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<ByteBuffer>
|
||||
{
|
||||
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<HttpField> 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<ByteBuffer> handler)
|
||||
{
|
||||
private FCGIHttpParser(ResponseHandler<ByteBuffer> 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<String, Object> 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<Connection> promise = (Promise<Connection>)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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HttpConnectionOverFCGI>
|
||||
{
|
||||
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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue