| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 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.parser; |
| |
| import java.nio.ByteBuffer; |
| 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; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| 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, listener); |
| this.listener = listener; |
| } |
| |
| @Override |
| public void noContent() |
| { |
| // Does nothing, since for responses the end of content is signaled via a FCGI_END_REQUEST frame |
| } |
| |
| @Override |
| protected boolean onContent(ByteBuffer buffer) |
| { |
| int request = getRequest(); |
| ResponseParser parser = parsers.get(request); |
| if (parser == null) |
| { |
| parser = new ResponseParser(listener, request); |
| parsers.put(request, parser); |
| } |
| return 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 seenResponseCode; |
| |
| private ResponseParser(ClientParser.Listener listener, int request) |
| { |
| this.listener = listener; |
| this.request = request; |
| this.httpParser = new FCGIHttpParser(this); |
| } |
| |
| public boolean parse(ByteBuffer buffer) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer); |
| |
| int remaining = buffer.remaining(); |
| while (remaining > 0) |
| { |
| switch (state) |
| { |
| case HEADERS: |
| { |
| if (httpParser.parseNext(buffer)) |
| state = State.CONTENT_MODE; |
| remaining = buffer.remaining(); |
| break; |
| } |
| case CONTENT_MODE: |
| { |
| // 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: |
| { |
| if (notifyContent(buffer)) |
| return true; |
| remaining = 0; |
| break; |
| } |
| case HTTP_CONTENT: |
| { |
| if (httpParser.parseNext(buffer)) |
| return true; |
| remaining = buffer.remaining(); |
| break; |
| } |
| default: |
| { |
| throw new IllegalStateException(); |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public int getHeaderCacheSize() |
| { |
| // TODO: configure this |
| return 0; |
| } |
| |
| @Override |
| public boolean startResponse(HttpVersion version, int status, String reason) |
| { |
| // The HTTP request line does not exist in FCGI responses |
| throw new IllegalStateException(); |
| } |
| |
| @Override |
| public boolean parsedHeader(HttpField httpField) |
| { |
| try |
| { |
| String name = httpField.getName(); |
| if ("Status".equalsIgnoreCase(name)) |
| { |
| if (!seenResponseCode) |
| { |
| seenResponseCode = true; |
| |
| // Need to set the response status so the |
| // HttpParser can handle the content properly. |
| String value = httpField.getValue(); |
| String[] parts = value.split(" "); |
| String status = parts[0]; |
| int code = Integer.parseInt(status); |
| httpParser.setResponseStatus(code); |
| String reason = parts.length > 1 ? value.substring(status.length()) : HttpStatus.getMessage(code); |
| |
| notifyBegin(code, reason.trim()); |
| notifyHeaders(fields); |
| } |
| } |
| else |
| { |
| fields.add(httpField); |
| if (seenResponseCode) |
| notifyHeader(httpField); |
| } |
| } |
| catch (Throwable x) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Exception while invoking listener " + listener, x); |
| } |
| return false; |
| } |
| |
| private void notifyBegin(int code, String reason) |
| { |
| try |
| { |
| listener.onBegin(request, code, reason); |
| } |
| catch (Throwable x) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Exception while invoking listener " + listener, x); |
| } |
| } |
| |
| private void notifyHeader(HttpField httpField) |
| { |
| try |
| { |
| listener.onHeader(request, httpField); |
| } |
| catch (Throwable x) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.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) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.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) |
| { |
| return notifyContent(buffer); |
| } |
| |
| private boolean notifyContent(ByteBuffer buffer) |
| { |
| try |
| { |
| return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); |
| } |
| catch (Throwable x) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Exception while invoking listener " + listener, x); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean messageComplete() |
| { |
| // 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; |
| } |
| |
| @Override |
| public void earlyEOF() |
| { |
| // TODO |
| } |
| |
| @Override |
| public void badMessage(int status, String reason) |
| { |
| // TODO |
| } |
| } |
| |
| // Methods overridden to make them visible here |
| private static class FCGIHttpParser extends HttpParser |
| { |
| private FCGIHttpParser(ResponseHandler<ByteBuffer> handler) |
| { |
| super(handler, 65 * 1024, true); |
| reset(); |
| } |
| |
| @Override |
| public void reset() |
| { |
| super.reset(); |
| setResponseStatus(200); |
| setState(State.HEADER); |
| } |
| |
| @Override |
| protected void setResponseStatus(int status) |
| { |
| super.setResponseStatus(status); |
| } |
| } |
| |
| private enum State |
| { |
| HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT |
| } |
| } |