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.nio.ByteBuffer;
22  
23  import org.eclipse.jetty.fcgi.FCGI;
24  import org.eclipse.jetty.http.HttpField;
25  import org.eclipse.jetty.util.log.Log;
26  import org.eclipse.jetty.util.log.Logger;
27  
28  /**
29   * <p>The FastCGI protocol exchanges <em>frames</em>.</p>
30   * <pre>
31   * struct frame {
32   *     ubyte version;
33   *     ubyte type;
34   *     ushort requestId;
35   *     ushort contentLength;
36   *     ubyte paddingLength;
37   *     ubyte reserved;
38   *     ubyte[] content;
39   *     ubyte[] padding;
40   * }
41   * </pre>
42   * <p>Depending on the {@code type}, the content may have a different format,
43   * so there are specialized content parsers.</p>
44   *
45   * @see HeaderParser
46   * @see ContentParser
47   */
48  public abstract class Parser
49  {
50      private static final Logger LOG = Log.getLogger(Parser.class);
51  
52      protected final HeaderParser headerParser = new HeaderParser();
53      private State state = State.HEADER;
54      private int padding;
55  
56      /**
57       * @param buffer the bytes to parse
58       * @return true if the caller should stop parsing, false if the caller should continue parsing
59       */
60      public boolean parse(ByteBuffer buffer)
61      {
62          while (true)
63          {
64              switch (state)
65              {
66                  case HEADER:
67                  {
68                      if (!headerParser.parse(buffer))
69                          return false;
70                      state = State.CONTENT;
71                      break;
72                  }
73                  case CONTENT:
74                  {
75                      ContentParser contentParser = findContentParser(headerParser.getFrameType());
76                      if (headerParser.getContentLength() == 0)
77                      {
78                          contentParser.noContent();
79                      }
80                      else
81                      {
82                          ContentParser.Result result = contentParser.parse(buffer);
83                          if (LOG.isDebugEnabled())
84                              LOG.debug("Parsed request {} content {} result={}", headerParser.getRequest(), headerParser.getFrameType(), result);
85  
86                          if (result == ContentParser.Result.PENDING)
87                          {
88                              // Not enough data, signal to read/parse more.
89                              return false;
90                          }
91                          if (result == ContentParser.Result.ASYNC)
92                          {
93                              // The content will be processed asynchronously, signal to stop
94                              // parsing; the async operation will eventually resume parsing.
95                              return true;
96                          }
97                      }
98                      padding = headerParser.getPaddingLength();
99                      state = State.PADDING;
100                     break;
101                 }
102                 case PADDING:
103                 {
104                     if (buffer.remaining() >= padding)
105                     {
106                         buffer.position(buffer.position() + padding);
107                         reset();
108                         break;
109                     }
110                     else
111                     {
112                         padding -= buffer.remaining();
113                         buffer.position(buffer.limit());
114                         return false;
115                     }
116                 }
117                 default:
118                 {
119                     throw new IllegalStateException();
120                 }
121             }
122         }
123     }
124 
125     protected abstract ContentParser findContentParser(FCGI.FrameType frameType);
126 
127     private void reset()
128     {
129         headerParser.reset();
130         state = State.HEADER;
131         padding = 0;
132     }
133 
134     public interface Listener
135     {
136         public void onHeader(int request, HttpField field);
137 
138         public void onHeaders(int request);
139 
140         /**
141          * @param request the request id
142          * @param stream the stream type
143          * @param buffer the content bytes
144          * @return true to signal to the parser to stop parsing, false to continue parsing
145          * @see Parser#parse(java.nio.ByteBuffer)
146          */
147         public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
148 
149         public void onEnd(int request);
150 
151         public void onFailure(int request, Throwable failure);
152 
153         public static class Adapter implements Listener
154         {
155             @Override
156             public void onHeader(int request, HttpField field)
157             {
158             }
159 
160             @Override
161             public void onHeaders(int request)
162             {
163             }
164 
165             @Override
166             public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
167             {
168                 return false;
169             }
170 
171             @Override
172             public void onEnd(int request)
173             {
174             }
175 
176             @Override
177             public void onFailure(int request, Throwable failure)
178             {
179 
180             }
181         }
182     }
183 
184     private enum State
185     {
186         HEADER, CONTENT, PADDING
187     }
188 }