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 boolean headerComplete()
57      {
58          headersComplete = true;
59          return super.headerComplete();
60      }
61  
62      private void dispatch()
63      {
64          synchronized (this)
65          {
66              if (dispatched)
67                  redispatch=true;
68              else
69              {
70                  LOG.debug("Dispatch {}", this);
71                  dispatched=true;
72                  execute(this);
73              }
74          }
75      }
76  
77      @Override
78      public void run()
79      {
80          boolean execute=true;
81          
82          while(execute)
83          {
84              try
85              {
86                  LOG.debug("Executing {}",this);
87                  super.run();
88              }
89              finally
90              {
91                  LOG.debug("Completing {}", this);
92                  synchronized (this)
93                  {
94                      dispatched = redispatch;
95                      redispatch=false;
96                      execute=dispatched;
97                  }
98              }
99          }
100     }
101     
102 
103     public void requestStart(final Fields headers, final boolean endRequest)
104     {
105         if (!headers.isEmpty())
106             requestHeaders(headers, endRequest);
107     }
108 
109     public void requestHeaders(Fields headers, boolean endRequest)
110     {
111         boolean proceed = performBeginRequest(headers);
112         if (!proceed)
113             return;
114 
115         performHeaders(headers);
116 
117         if (endRequest)
118         {
119             boolean dispatch = headerComplete();
120             if (messageComplete())
121                 dispatch=true;
122             if (dispatch)
123                 dispatch();
124         }
125     }
126 
127     public void requestContent(final DataInfo dataInfo, boolean endRequest)
128     {
129         boolean dispatch=false;
130         if (!headersComplete && headerComplete())
131             dispatch=true;
132 
133         LOG.debug("HTTP > {} bytes of content", dataInfo.length());
134 
135         // We need to copy the dataInfo since we do not know when its bytes
136         // will be consumed. When the copy is consumed, we consume also the
137         // original, so the implementation can send a window update.
138         ByteBuffer copyByteBuffer = dataInfo.asByteBuffer(false);
139         ByteBufferDataInfo copyDataInfo = new ByteBufferDataInfo(copyByteBuffer, dataInfo.isClose())
140         {
141             @Override
142             public void consume(int delta)
143             {
144                 super.consume(delta);
145                 dataInfo.consume(delta);
146             }
147         };
148         LOG.debug("Queuing last={} content {}", endRequest, copyDataInfo);
149 
150         if (content(copyDataInfo))
151             dispatch=true;
152 
153         if (endRequest && messageComplete())
154             dispatch=true;
155         
156         if (dispatch)
157             dispatch();
158     }
159 
160     private boolean performBeginRequest(Fields headers)
161     {
162         short version = stream.getSession().getVersion();
163         Fields.Field methodHeader = headers.get(HTTPSPDYHeader.METHOD.name(version));
164         Fields.Field uriHeader = headers.get(HTTPSPDYHeader.URI.name(version));
165         Fields.Field versionHeader = headers.get(HTTPSPDYHeader.VERSION.name(version));
166 
167         if (methodHeader == null || uriHeader == null || versionHeader == null)
168         {
169             badMessage(400, "Missing required request line elements");
170             return false;
171         }
172 
173         HttpMethod httpMethod = HttpMethod.fromString(methodHeader.getValue());
174         HttpVersion httpVersion = HttpVersion.fromString(versionHeader.getValue());
175 
176         // TODO should handle URI as byte buffer as some bad clients send WRONG encodings in query string
177         // that  we have to deal with
178         ByteBuffer uri = BufferUtil.toBuffer(uriHeader.getValue());
179 
180         LOG.debug("HTTP > {} {} {}", httpMethod, uriHeader.getValue(), httpVersion);
181         startRequest(httpMethod, httpMethod.asString(), uri, httpVersion);
182 
183         Fields.Field schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(version));
184         if (schemeHeader != null)
185             getRequest().setScheme(schemeHeader.getValue());
186         return true;
187     }
188 
189     private void performHeaders(Fields headers)
190     {
191         for (Fields.Field header : headers)
192         {
193             String name = header.getName();
194 
195             // Skip special SPDY headers, unless it's the "host" header
196             HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(stream.getSession().getVersion(), name);
197             if (specialHeader != null)
198             {
199                 if (specialHeader == HTTPSPDYHeader.HOST)
200                     name = "host";
201                 else
202                     continue;
203             }
204 
205             switch (name)
206             {
207                 case "connection":
208                 case "keep-alive":
209                 case "proxy-connection":
210                 case "transfer-encoding":
211                 {
212                     // Spec says to ignore these headers
213                     continue;
214                 }
215                 default:
216                 {
217                     // Spec says headers must be single valued
218                     String value = header.getValue();
219                     LOG.debug("HTTP > {}: {}", name, value);
220                     parsedHeader(new HttpField(name,value));
221                     break;
222                 }
223             }
224         }
225     }
226 }