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.spdy.server.http;
20  
21  import java.nio.ByteBuffer;
22  
23  import org.eclipse.jetty.http.HttpField;
24  import org.eclipse.jetty.http.HttpMethod;
25  import org.eclipse.jetty.http.HttpVersion;
26  import org.eclipse.jetty.io.EndPoint;
27  import org.eclipse.jetty.server.Connector;
28  import org.eclipse.jetty.server.HttpChannel;
29  import org.eclipse.jetty.server.HttpConfiguration;
30  import org.eclipse.jetty.server.HttpTransport;
31  import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
32  import org.eclipse.jetty.spdy.api.DataInfo;
33  import org.eclipse.jetty.spdy.api.Stream;
34  import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
35  import org.eclipse.jetty.util.BufferUtil;
36  import org.eclipse.jetty.util.Fields;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  
40  public class HttpChannelOverSPDY extends HttpChannel<DataInfo>
41  {
42      private static final Logger LOG = Log.getLogger(HttpChannelOverSPDY.class);
43  
44      private final Stream stream;
45      private boolean dispatched; // Guarded by synchronization on tasks
46      private boolean redispatch; // Guarded by synchronization on tasks
47      private boolean headersComplete;
48  
49      public HttpChannelOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInputOverSPDY input, Stream stream)
50      {
51          super(connector, configuration, endPoint, transport, input);
52          this.stream = stream;
53      }
54  
55      @Override
56      public long getIdleTimeout()
57      {
58          return stream.getIdleTimeout();
59      }
60      
61      @Override
62      public boolean headerComplete()
63      {
64          headersComplete = true;
65          return super.headerComplete();
66      }
67  
68      private void dispatch()
69      {
70          synchronized (this)
71          {
72              if (dispatched)
73                  redispatch=true;
74              else
75              {
76                  if (LOG.isDebugEnabled())
77                      LOG.debug("Dispatch {}", this);
78                  dispatched=true;
79                  execute(this);
80              }
81          }
82      }
83  
84      @Override
85      public void run()
86      {
87          boolean execute=true;
88          
89          while(execute)
90          {
91              try
92              {
93                  if (LOG.isDebugEnabled())
94                      LOG.debug("Executing {}",this);
95                  super.run();
96              }
97              finally
98              {
99                  if (LOG.isDebugEnabled())
100                     LOG.debug("Completing {}", this);
101                 synchronized (this)
102                 {
103                     dispatched = redispatch;
104                     redispatch=false;
105                     execute=dispatched;
106                 }
107             }
108         }
109     }
110     
111 
112     public void requestStart(final Fields headers, final boolean endRequest)
113     {
114         if (!headers.isEmpty())
115             requestHeaders(headers, endRequest);
116     }
117 
118     public void requestHeaders(Fields headers, boolean endRequest)
119     {
120         boolean proceed = performBeginRequest(headers);
121         if (!proceed)
122             return;
123 
124         performHeaders(headers);
125 
126         if (endRequest)
127         {
128             boolean dispatch = headerComplete();
129             if (messageComplete())
130                 dispatch=true;
131             if (dispatch)
132                 dispatch();
133         }
134     }
135 
136     public void requestContent(final DataInfo dataInfo, boolean endRequest)
137     {
138         boolean dispatch=false;
139         if (!headersComplete && headerComplete())
140             dispatch=true;
141 
142         if (LOG.isDebugEnabled())
143             LOG.debug("HTTP > {} bytes of content", dataInfo.length());
144 
145         // We need to copy the dataInfo since we do not know when its bytes
146         // will be consumed. When the copy is consumed, we consume also the
147         // original, so the implementation can send a window update.
148         ByteBuffer copyByteBuffer = dataInfo.asByteBuffer(false);
149         ByteBufferDataInfo copyDataInfo = new ByteBufferDataInfo(copyByteBuffer, dataInfo.isClose())
150         {
151             @Override
152             public void consume(int delta)
153             {
154                 super.consume(delta);
155                 dataInfo.consume(delta);
156             }
157         };
158         if (LOG.isDebugEnabled())
159             LOG.debug("Queuing last={} content {}", endRequest, copyDataInfo);
160 
161         if (content(copyDataInfo))
162             dispatch=true;
163 
164         if (endRequest && messageComplete())
165             dispatch=true;
166         
167         if (dispatch)
168             dispatch();
169     }
170     
171     @Override
172     public boolean messageComplete()
173     {
174         super.messageComplete();
175         return false;
176     }
177 
178     private boolean performBeginRequest(Fields headers)
179     {
180         short version = stream.getSession().getVersion();
181         Fields.Field methodHeader = headers.get(HTTPSPDYHeader.METHOD.name(version));
182         Fields.Field uriHeader = headers.get(HTTPSPDYHeader.URI.name(version));
183         Fields.Field versionHeader = headers.get(HTTPSPDYHeader.VERSION.name(version));
184 
185         if (methodHeader == null || uriHeader == null || versionHeader == null)
186         {
187             badMessage(400, "Missing required request line elements");
188             return false;
189         }
190 
191         HttpMethod httpMethod = HttpMethod.fromString(methodHeader.getValue());
192         HttpVersion httpVersion = HttpVersion.fromString(versionHeader.getValue());
193 
194         // TODO should handle URI as byte buffer as some bad clients send WRONG encodings in query string
195         // that  we have to deal with
196         ByteBuffer uri = BufferUtil.toBuffer(uriHeader.getValue());
197 
198         if (LOG.isDebugEnabled())
199             LOG.debug("HTTP > {} {} {}", httpMethod, uriHeader.getValue(), httpVersion);
200         startRequest(httpMethod, httpMethod.asString(), uri, httpVersion);
201 
202         Fields.Field schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(version));
203         if (schemeHeader != null)
204             getRequest().setScheme(schemeHeader.getValue());
205         return true;
206     }
207 
208     private void performHeaders(Fields headers)
209     {
210         for (Fields.Field header : headers)
211         {
212             String name = header.getName();
213 
214             // Skip special SPDY headers, unless it's the "host" header
215             HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(stream.getSession().getVersion(), name);
216             if (specialHeader != null)
217             {
218                 if (specialHeader == HTTPSPDYHeader.HOST)
219                     name = "host";
220                 else
221                     continue;
222             }
223 
224             switch (name)
225             {
226                 case "connection":
227                 case "keep-alive":
228                 case "proxy-connection":
229                 case "transfer-encoding":
230                 {
231                     // Spec says to ignore these headers
232                     continue;
233                 }
234                 default:
235                 {
236                     // Spec says headers must be single valued
237                     String value = header.getValue();
238                     if (LOG.isDebugEnabled())
239                         LOG.debug("HTTP > {}: {}", name, value);
240                     parsedHeader(new HttpField(name,value));
241                     break;
242                 }
243             }
244         }
245     }
246 }