View Javadoc

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