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 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<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 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 boolean 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             return false;
194         }
195 
196         private void notifyBegin(int code, String reason)
197         {
198             try
199             {
200                 listener.onBegin(request, code, reason);
201             }
202             catch (Throwable x)
203             {
204                 if (LOG.isDebugEnabled())
205                     LOG.debug("Exception while invoking listener " + listener, x);
206             }
207         }
208 
209         private void notifyHeader(HttpField httpField)
210         {
211             try
212             {
213                 listener.onHeader(request, httpField);
214             }
215             catch (Throwable x)
216             {
217                 if (LOG.isDebugEnabled())
218                     LOG.debug("Exception while invoking listener " + listener, x);
219             }
220         }
221 
222         private void notifyHeaders(HttpFields fields)
223         {
224             if (fields != null)
225             {
226                 for (HttpField field : fields)
227                     notifyHeader(field);
228             }
229         }
230 
231         private void notifyHeaders()
232         {
233             try
234             {
235                 listener.onHeaders(request);
236             }
237             catch (Throwable x)
238             {
239                 if (LOG.isDebugEnabled())
240                     LOG.debug("Exception while invoking listener " + listener, x);
241             }
242         }
243 
244         @Override
245         public boolean headerComplete()
246         {
247             if (!seenResponseCode)
248             {
249                 // No Status header but we have other headers, assume 200 OK
250                 notifyBegin(200, "OK");
251                 notifyHeaders(fields);
252             }
253             notifyHeaders();
254             // Return from parsing so that we can parse the content
255             return true;
256         }
257 
258         @Override
259         public boolean content(ByteBuffer buffer)
260         {
261             return notifyContent(buffer);
262         }
263 
264         private boolean notifyContent(ByteBuffer buffer)
265         {
266             try
267             {
268                 return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
269             }
270             catch (Throwable x)
271             {
272                 if (LOG.isDebugEnabled())
273                     LOG.debug("Exception while invoking listener " + listener, x);
274                 return false;
275             }
276         }
277 
278         @Override
279         public boolean messageComplete()
280         {
281             // Return from parsing so that we can parse the next headers or the raw content.
282             // No need to notify the listener because it will be done by FCGI_END_REQUEST.
283             return true;
284         }
285 
286         @Override
287         public void earlyEOF()
288         {
289             // TODO
290         }
291 
292         @Override
293         public void badMessage(int status, String reason)
294         {
295             // TODO
296         }
297     }
298 
299     // Methods overridden to make them visible here
300     private static class FCGIHttpParser extends HttpParser
301     {
302         private FCGIHttpParser(ResponseHandler<ByteBuffer> handler)
303         {
304             super(handler, 65 * 1024, true);
305             reset();
306         }
307 
308         @Override
309         public void reset()
310         {
311             super.reset();
312             setResponseStatus(200);
313             setState(State.HEADER);
314         }
315 
316         @Override
317         protected void setResponseStatus(int status)
318         {
319             super.setResponseStatus(status);
320         }
321     }
322 
323     private enum State
324     {
325         HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT
326     }
327 }