View Javadoc

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