View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.fcgi.parser;
20  
21  import java.io.EOFException;
22  import java.nio.ByteBuffer;
23  import java.util.Map;
24  import java.util.concurrent.ConcurrentHashMap;
25  
26  import org.eclipse.jetty.fcgi.FCGI;
27  import org.eclipse.jetty.http.BadMessageException;
28  import org.eclipse.jetty.http.HttpField;
29  import org.eclipse.jetty.http.HttpFields;
30  import org.eclipse.jetty.http.HttpHeader;
31  import org.eclipse.jetty.http.HttpParser;
32  import org.eclipse.jetty.http.HttpStatus;
33  import org.eclipse.jetty.http.HttpVersion;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  
37  /**
38   * <p>The parser for STDOUT type frame bodies.</p>
39   * <p>STDOUT frame bodies contain both the HTTP headers (but not the response line)
40   * and the HTTP content (either Content-Length delimited or chunked).</p>
41   * <p>For this reason, a special HTTP parser is used to parse the frames body.
42   * This special HTTP parser is configured to skip the response line, and to
43   * parse HTTP headers and HTTP content.</p>
44   */
45  public class ResponseContentParser extends StreamContentParser
46  {
47      private static final Logger LOG = Log.getLogger(ResponseContentParser.class);
48  
49      private final Map<Integer, ResponseParser> parsers = new ConcurrentHashMap<>();
50      private final ClientParser.Listener listener;
51  
52      public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener)
53      {
54          super(headerParser, FCGI.StreamType.STD_OUT, listener);
55          this.listener = listener;
56      }
57  
58      @Override
59      public void noContent()
60      {
61          // Does nothing, since for responses the end of content is signaled via a FCGI_END_REQUEST frame
62      }
63  
64      @Override
65      protected boolean onContent(ByteBuffer buffer)
66      {
67          int request = getRequest();
68          ResponseParser parser = parsers.get(request);
69          if (parser == null)
70          {
71              parser = new ResponseParser(listener, request);
72              parsers.put(request, parser);
73          }
74          return parser.parse(buffer);
75      }
76  
77      @Override
78      protected void end(int request)
79      {
80          super.end(request);
81          parsers.remove(request);
82      }
83  
84      private class ResponseParser implements HttpParser.ResponseHandler
85      {
86          private final HttpFields fields = new HttpFields();
87          private ClientParser.Listener listener;
88          private final int request;
89          private final FCGIHttpParser httpParser;
90          private State state = State.HEADERS;
91          private boolean seenResponseCode;
92  
93          private ResponseParser(ClientParser.Listener listener, int request)
94          {
95              this.listener = listener;
96              this.request = request;
97              this.httpParser = new FCGIHttpParser(this);
98          }
99  
100         public boolean parse(ByteBuffer buffer)
101         {
102             int remaining = buffer.remaining();
103             while (remaining > 0)
104             {
105                 if (LOG.isDebugEnabled())
106                     LOG.debug("Response {} {}, state {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
107 
108                 switch (state)
109                 {
110                     case HEADERS:
111                     {
112                         if (httpParser.parseNext(buffer))
113                             state = State.CONTENT_MODE;
114                         remaining = buffer.remaining();
115                         break;
116                     }
117                     case CONTENT_MODE:
118                     {
119                         // If we have no indication of the content, then
120                         // the HTTP parser will assume there is no content
121                         // and will not parse it even if it is provided,
122                         // so we have to parse it raw ourselves here.
123                         boolean rawContent = fields.size() == 0 ||
124                                 (fields.get(HttpHeader.CONTENT_LENGTH) == null &&
125                                         fields.get(HttpHeader.TRANSFER_ENCODING) == null);
126                         state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT;
127                         break;
128                     }
129                     case RAW_CONTENT:
130                     {
131                         if (notifyContent(buffer))
132                             return true;
133                         remaining = 0;
134                         break;
135                     }
136                     case HTTP_CONTENT:
137                     {
138                         if (httpParser.parseNext(buffer))
139                             return true;
140                         remaining = buffer.remaining();
141                         break;
142                     }
143                     default:
144                     {
145                         throw new IllegalStateException();
146                     }
147                 }
148             }
149             return false;
150         }
151 
152         @Override
153         public int getHeaderCacheSize()
154         {
155             // TODO: configure this
156             return 0;
157         }
158 
159         @Override
160         public boolean startResponse(HttpVersion version, int status, String reason)
161         {
162             // The HTTP request line does not exist in FCGI responses
163             throw new IllegalStateException();
164         }
165 
166         @Override
167         public void parsedHeader(HttpField httpField)
168         {
169             try
170             {
171                 String name = httpField.getName();
172                 if ("Status".equalsIgnoreCase(name))
173                 {
174                     if (!seenResponseCode)
175                     {
176                         seenResponseCode = true;
177 
178                         // Need to set the response status so the
179                         // HttpParser can handle the content properly.
180                         String value = httpField.getValue();
181                         String[] parts = value.split(" ");
182                         String status = parts[0];
183                         int code = Integer.parseInt(status);
184                         httpParser.setResponseStatus(code);
185                         String reason = parts.length > 1 ? value.substring(status.length()) : HttpStatus.getMessage(code);
186 
187                         notifyBegin(code, reason.trim());
188                         notifyHeaders(fields);
189                     }
190                 }
191                 else
192                 {
193                     fields.add(httpField);
194                     if (seenResponseCode)
195                         notifyHeader(httpField);
196                 }
197             }
198             catch (Throwable x)
199             {
200                 if (LOG.isDebugEnabled())
201                     LOG.debug("Exception while invoking listener " + listener, x);
202             }
203         }
204 
205         private void notifyBegin(int code, String reason)
206         {
207             try
208             {
209                 listener.onBegin(request, code, reason);
210             }
211             catch (Throwable x)
212             {
213                 if (LOG.isDebugEnabled())
214                     LOG.debug("Exception while invoking listener " + listener, x);
215             }
216         }
217 
218         private void notifyHeader(HttpField httpField)
219         {
220             try
221             {
222                 listener.onHeader(request, httpField);
223             }
224             catch (Throwable x)
225             {
226                 if (LOG.isDebugEnabled())
227                     LOG.debug("Exception while invoking listener " + listener, x);
228             }
229         }
230 
231         private void notifyHeaders(HttpFields fields)
232         {
233             if (fields != null)
234             {
235                 for (HttpField field : fields)
236                     notifyHeader(field);
237             }
238         }
239 
240         private void notifyHeaders()
241         {
242             try
243             {
244                 listener.onHeaders(request);
245             }
246             catch (Throwable x)
247             {
248                 if (LOG.isDebugEnabled())
249                     LOG.debug("Exception while invoking listener " + listener, x);
250             }
251         }
252 
253         @Override
254         public boolean headerComplete()
255         {
256             if (!seenResponseCode)
257             {
258                 // No Status header but we have other headers, assume 200 OK.
259                 notifyBegin(200, "OK");
260                 notifyHeaders(fields);
261             }
262             notifyHeaders();
263             // Return from HTTP parsing so that we can parse the content.
264             return true;
265         }
266 
267         @Override
268         public boolean content(ByteBuffer buffer)
269         {
270             return notifyContent(buffer);
271         }
272 
273         private boolean notifyContent(ByteBuffer buffer)
274         {
275             try
276             {
277                 return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
278             }
279             catch (Throwable x)
280             {
281                 if (LOG.isDebugEnabled())
282                     LOG.debug("Exception while invoking listener " + listener, x);
283                 return false;
284             }
285         }
286 
287         @Override
288         public boolean messageComplete()
289         {
290             // No need to notify the end of the response to the
291             // listener because it will be done by FCGI_END_REQUEST.
292             return false;
293         }
294 
295         @Override
296         public void earlyEOF()
297         {
298             fail(new EOFException());
299         }
300 
301         @Override
302         public void badMessage(int status, String reason)
303         {
304             fail(new BadMessageException(status, reason));
305         }
306 
307         protected void fail(Throwable failure)
308         {
309             try
310             {
311                 listener.onFailure(request, failure);
312             }
313             catch (Throwable x)
314             {
315                 if (LOG.isDebugEnabled())
316                     LOG.debug("Exception while invoking listener " + listener, x);
317             }
318         }
319     }
320 
321     // Methods overridden to make them visible here
322     private static class FCGIHttpParser extends HttpParser
323     {
324         private FCGIHttpParser(ResponseHandler handler)
325         {
326             super(handler, 65 * 1024, true);
327             reset();
328         }
329 
330         @Override
331         public void reset()
332         {
333             super.reset();
334             setResponseStatus(200);
335             setState(State.HEADER);
336         }
337 
338         @Override
339         protected void setResponseStatus(int status)
340         {
341             super.setResponseStatus(status);
342         }
343     }
344 
345     private enum State
346     {
347         HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT
348     }
349 }