View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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 boolean 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          return 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
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 boolean parse(ByteBuffer buffer)
91          {
92              if (LOG.isDebugEnabled())
93                  LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
94  
95              int remaining = buffer.remaining();
96              while (remaining > 0)
97              {
98                  switch (state)
99                  {
100                     case HEADERS:
101                     {
102                         if (httpParser.parseNext(buffer))
103                             state = State.CONTENT_MODE;
104                         remaining = buffer.remaining();
105                         break;
106                     }
107                     case CONTENT_MODE:
108                     {
109                         // If we have no indication of the content, then
110                         // the HTTP parser will assume there is no content
111                         // and will not parse it even if it is provided,
112                         // so we have to parse it raw ourselves here.
113                         boolean rawContent = fields.size() == 0 ||
114                                 (fields.get(HttpHeader.CONTENT_LENGTH) == null &&
115                                         fields.get(HttpHeader.TRANSFER_ENCODING) == null);
116                         state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT;
117                         break;
118                     }
119                     case RAW_CONTENT:
120                     {
121                         if (notifyContent(buffer))
122                             return true;
123                         remaining = 0;
124                         break;
125                     }
126                     case HTTP_CONTENT:
127                     {
128                         if (httpParser.parseNext(buffer))
129                             return true;
130                         remaining = buffer.remaining();
131                         break;
132                     }
133                     default:
134                     {
135                         throw new IllegalStateException();
136                     }
137                 }
138             }
139             return false;
140         }
141 
142         @Override
143         public int getHeaderCacheSize()
144         {
145             // TODO: configure this
146             return 0;
147         }
148 
149         @Override
150         public boolean startResponse(HttpVersion version, int status, String reason)
151         {
152             // The HTTP request line does not exist in FCGI responses
153             throw new IllegalStateException();
154         }
155 
156         @Override
157         public void parsedHeader(HttpField httpField)
158         {
159             try
160             {
161                 String name = httpField.getName();
162                 if ("Status".equalsIgnoreCase(name))
163                 {
164                     if (!seenResponseCode)
165                     {
166                         seenResponseCode = true;
167 
168                         // Need to set the response status so the
169                         // HttpParser can handle the content properly.
170                         String value = httpField.getValue();
171                         String[] parts = value.split(" ");
172                         String status = parts[0];
173                         int code = Integer.parseInt(status);
174                         httpParser.setResponseStatus(code);
175                         String reason = parts.length > 1 ? value.substring(status.length()) : HttpStatus.getMessage(code);
176 
177                         notifyBegin(code, reason.trim());
178                         notifyHeaders(fields);
179                     }
180                 }
181                 else
182                 {
183                     fields.add(httpField);
184                     if (seenResponseCode)
185                         notifyHeader(httpField);
186                 }
187             }
188             catch (Throwable x)
189             {
190                 if (LOG.isDebugEnabled())
191                     LOG.debug("Exception while invoking listener " + listener, x);
192             }
193         }
194 
195         private void notifyBegin(int code, String reason)
196         {
197             try
198             {
199                 listener.onBegin(request, code, reason);
200             }
201             catch (Throwable x)
202             {
203                 if (LOG.isDebugEnabled())
204                     LOG.debug("Exception while invoking listener " + listener, x);
205             }
206         }
207 
208         private void notifyHeader(HttpField httpField)
209         {
210             try
211             {
212                 listener.onHeader(request, httpField);
213             }
214             catch (Throwable x)
215             {
216                 if (LOG.isDebugEnabled())
217                     LOG.debug("Exception while invoking listener " + listener, x);
218             }
219         }
220 
221         private void notifyHeaders(HttpFields fields)
222         {
223             if (fields != null)
224             {
225                 for (HttpField field : fields)
226                     notifyHeader(field);
227             }
228         }
229 
230         private void notifyHeaders()
231         {
232             try
233             {
234                 listener.onHeaders(request);
235             }
236             catch (Throwable x)
237             {
238                 if (LOG.isDebugEnabled())
239                     LOG.debug("Exception while invoking listener " + listener, x);
240             }
241         }
242 
243         @Override
244         public boolean headerComplete()
245         {
246             if (!seenResponseCode)
247             {
248                 // No Status header but we have other headers, assume 200 OK
249                 notifyBegin(200, "OK");
250                 notifyHeaders(fields);
251             }
252             notifyHeaders();
253             // Return from parsing so that we can parse the content
254             return true;
255         }
256 
257         @Override
258         public boolean content(ByteBuffer buffer)
259         {
260             return notifyContent(buffer);
261         }
262 
263         private boolean notifyContent(ByteBuffer buffer)
264         {
265             try
266             {
267                 return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
268             }
269             catch (Throwable x)
270             {
271                 if (LOG.isDebugEnabled())
272                     LOG.debug("Exception while invoking listener " + listener, x);
273                 return false;
274             }
275         }
276 
277         @Override
278         public boolean messageComplete()
279         {
280             // Return from parsing so that we can parse the next headers or the raw content.
281             // No need to notify the listener because it will be done by FCGI_END_REQUEST.
282             return true;
283         }
284 
285         @Override
286         public void earlyEOF()
287         {
288             // TODO
289         }
290 
291         @Override
292         public void badMessage(int status, String reason)
293         {
294             // TODO
295         }
296     }
297 
298     // Methods overridden to make them visible here
299     private static class FCGIHttpParser extends HttpParser
300     {
301         private FCGIHttpParser(ResponseHandler handler)
302         {
303             super(handler, 65 * 1024, true);
304             reset();
305         }
306 
307         @Override
308         public void reset()
309         {
310             super.reset();
311             setResponseStatus(200);
312             setState(State.HEADER);
313         }
314 
315         @Override
316         protected void setResponseStatus(int status)
317         {
318             super.setResponseStatus(status);
319         }
320     }
321 
322     private enum State
323     {
324         HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT
325     }
326 }