View Javadoc

1   // ========================================================================
2   // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.client;
16  
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.InterruptedIOException;
20  
21  import org.mortbay.io.Buffer;
22  import org.mortbay.io.Buffers;
23  import org.mortbay.io.ByteArrayBuffer;
24  import org.mortbay.io.Connection;
25  import org.mortbay.io.EndPoint;
26  import org.mortbay.io.nio.SelectChannelEndPoint;
27  import org.mortbay.jetty.HttpGenerator;
28  import org.mortbay.jetty.HttpHeaderValues;
29  import org.mortbay.jetty.HttpHeaders;
30  import org.mortbay.jetty.HttpParser;
31  import org.mortbay.jetty.HttpSchemes;
32  import org.mortbay.jetty.HttpVersions;
33  import org.mortbay.jetty.client.security.Authorization;
34  import org.mortbay.jetty.security.SslHttpChannelEndPoint;
35  import org.mortbay.log.Log;
36  import org.mortbay.thread.Timeout;
37  
38  /**
39   * 
40   * @author Greg Wilkins
41   * @author Guillaume Nodet
42   */
43  public class HttpConnection implements Connection
44  {
45      HttpDestination _destination;
46      EndPoint _endp;
47      HttpGenerator _generator;
48      HttpParser _parser;
49      boolean _http11 = true;
50      Buffer _connectionHeader;
51      Buffer _requestContentChunk;
52      long _last;
53      boolean _requestComplete;
54      public String _message;
55      public Throwable _throwable;
56  
57      /* The current exchange waiting for a response */
58      HttpExchange _exchange;
59      HttpExchange _pipeline;
60  
61      public void dump() throws IOException
62      {
63          System.err.println("endp=" + _endp + " " + _endp.isBufferingInput() + " " + _endp.isBufferingOutput());
64          System.err.println("generator=" + _generator);
65          System.err.println("parser=" + _parser.getState() + " " + _parser.isMoreInBuffer());
66          System.err.println("exchange=" + _exchange);
67          if (_endp instanceof SslHttpChannelEndPoint)
68              ((SslHttpChannelEndPoint)_endp).dump();
69      }
70  
71      Timeout.Task _timeout = new Timeout.Task()
72      {
73          public void expire()
74          {
75              HttpExchange ex = null;
76              try
77              {
78                  synchronized (HttpConnection.this)
79                  {
80                      ex = _exchange;
81                      _exchange = null;
82                      if (ex != null)
83                          _destination.returnConnection(HttpConnection.this,true);
84                  }
85              }
86              catch (Exception e)
87              {
88                  Log.debug(e);
89              }
90              finally
91              {
92                  try
93                  {
94                      _endp.close();
95                  }
96                  catch (IOException e)
97                  {
98                      Log.ignore(e);
99                  }
100 
101                 if (ex.getStatus() < HttpExchange.STATUS_COMPLETED)
102                 {
103                     ex.setStatus(HttpExchange.STATUS_EXPIRED);
104                 }
105             }
106         }
107 
108     };
109 
110     /* ------------------------------------------------------------ */
111     HttpConnection(Buffers buffers, EndPoint endp, int hbs, int cbs)
112     {
113         _endp = endp;
114         _generator = new HttpGenerator(buffers,endp,hbs,cbs);
115         _parser = new HttpParser(buffers,endp,new Handler(),hbs,cbs);
116     }
117 
118     /* ------------------------------------------------------------ */
119     public HttpDestination getDestination()
120     {
121         return _destination;
122     }
123 
124     /* ------------------------------------------------------------ */
125     public void setDestination(HttpDestination destination)
126     {
127         _destination = destination;
128     }
129 
130     /* ------------------------------------------------------------ */
131     public boolean send(HttpExchange ex) throws IOException
132     {
133         // _message =
134         // Thread.currentThread().getName()+": Generator instance="+_generator
135         // .hashCode()+" state= "+_generator.getState()+" _exchange="+_exchange;
136         _throwable = new Throwable();
137         synchronized (this)
138         {
139             if (_exchange != null)
140             {
141                 if (_pipeline != null)
142                     throw new IllegalStateException(this + " PIPELINED!!!  _exchange=" + _exchange);
143                 _pipeline = ex;
144                 return true;
145             }
146 
147             if (!_endp.isOpen())
148                 return false;
149 
150             ex.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT);
151             _exchange = ex;
152 
153             if (_endp.isBlocking())
154                 this.notify();
155             else
156             {
157                 SelectChannelEndPoint scep = (SelectChannelEndPoint)_endp;
158                 scep.scheduleWrite();
159             }
160 
161             if (!_endp.isBlocking())
162                 _destination.getHttpClient().schedule(_timeout);
163 
164             return true;
165         }
166     }
167 
168     /* ------------------------------------------------------------ */
169     public void handle() throws IOException
170     {
171         int no_progress = 0;
172         long flushed = 0;
173 
174         boolean failed = false;
175         while (_endp.isBufferingInput() || _endp.isOpen())
176         {
177             synchronized (this)
178             {
179                 while (_exchange == null)
180                 {
181                     if (_endp.isBlocking())
182                     {
183                         try
184                         {
185                             this.wait();
186                         }
187                         catch (InterruptedException e)
188                         {
189                             throw new InterruptedIOException();
190                         }
191                     }
192                     else
193                     {
194                         // Hopefully just space?
195                         _parser.fill();
196                         _parser.skipCRLF();
197                         if (_parser.isMoreInBuffer())
198                         {
199                             Log.warn("unexpected data");
200                             _endp.close();
201                         }
202 
203                         return;
204                     }
205                 }
206             }
207             if (_exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
208             {
209                 no_progress = 0;
210                 commitRequest();
211             }
212 
213             try
214             {
215                 long io = 0;
216 
217                 if (_generator.isComplete())
218                 {
219                     if (!_requestComplete)
220                     {
221                         _requestComplete = true;
222                         _exchange.getEventListener().onRequestComplete();
223                     }
224                 }
225                 else
226                 {
227                     // Write as much of the request as possible
228                     synchronized (this)
229                     {
230                         if (_exchange == null)
231                             continue;
232                         flushed = _generator.flush();
233                         io += flushed;
234                     }
235 
236                     if (!_generator.isComplete())
237                     {
238                         InputStream in = _exchange.getRequestContentSource();
239                         if (in != null)
240                         {
241                             if (_requestContentChunk == null || _requestContentChunk.length() == 0)
242                             {
243                                 _requestContentChunk = _exchange.getRequestContentChunk();
244                                 if (_requestContentChunk != null)
245                                     _generator.addContent(_requestContentChunk,false);
246                                 else
247                                     _generator.complete();
248                                 io += _generator.flush();
249                             }
250                         }
251                         else
252                             _generator.complete();
253                     }
254                 }
255 
256                 // If we are not ended then parse available
257                 if (!_parser.isComplete() && _generator.isCommitted())
258                 {
259                     long filled = _parser.parseAvailable();
260                     io += filled;
261                 }
262 
263                 if (io > 0)
264                     no_progress = 0;
265                 else if (no_progress++ >= 2 && !_endp.isBlocking()) // TODO maybe no retries is best here?
266                     return;
267             }
268             catch (IOException e)
269             {
270                 synchronized (this)
271                 {
272                     if (_exchange != null)
273                     {
274                         _exchange.getEventListener().onException(e);
275                         _exchange.setStatus(HttpExchange.STATUS_EXCEPTED);
276                     }
277                 }
278                 failed = true;
279                 throw e;
280             }
281             finally
282             {
283                 boolean complete = false;
284                 boolean close = failed; // always close the connection on error
285                 if (!failed)
286                 {
287                     // are we complete?
288                     if (_generator.isComplete())
289                     {
290                         if (!_requestComplete)
291                         {
292                             _requestComplete = true;
293                             _exchange.getEventListener().onRequestComplete();
294                         }
295 
296                         // we need to return the HttpConnection to a state that
297                         // it can be reused or closed out
298                         if (_parser.isComplete())
299                         {
300                             _destination.getHttpClient().cancel(_timeout);
301                             complete = true;
302                         }
303                     }
304                 }
305 
306                 if (complete || failed)
307                 {
308                     synchronized (this)
309                     {
310                         if (!close)
311                             close = shouldClose();
312                             
313                         reset(true);
314                         no_progress = 0;
315                         flushed = -1;
316                         if (_exchange != null)
317                         {
318                             _exchange = null;
319 
320                             if (_pipeline == null)
321                             {
322                                 _destination.returnConnection(this,close);
323                                 if (close)
324                                     return;
325                             }
326                             else
327                             {
328                                 if (close)
329                                 {
330                                     _destination.returnConnection(this,close);
331                                     _destination.send(_pipeline);
332                                     _pipeline = null;
333                                     return;
334                                 }
335 
336                                 HttpExchange ex = _pipeline;
337                                 _pipeline = null;
338 
339                                 send(ex);
340                             }
341                         }
342                     }
343                 }
344             }
345         }
346     }
347 
348     /* ------------------------------------------------------------ */
349     public boolean isIdle()
350     {
351         synchronized (this)
352         {
353             return _exchange == null;
354         }
355     }
356 
357     /* ------------------------------------------------------------ */
358     public EndPoint getEndPoint()
359     {
360         return _endp;
361     }
362 
363     /* ------------------------------------------------------------ */
364     private void commitRequest() throws IOException
365     {
366         synchronized (this)
367         {
368             if (_exchange.getStatus() != HttpExchange.STATUS_WAITING_FOR_COMMIT)
369                 throw new IllegalStateException();
370 
371             _exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
372             _generator.setVersion(_exchange._version);
373 
374             String uri = _exchange._uri;
375             if (_destination.isProxied() && uri.startsWith("/"))
376             {
377                 // TODO suppress port 80 or 443
378                 uri = (_destination.isSecure()?HttpSchemes.HTTPS:HttpSchemes.HTTP) + "://" + _destination.getAddress().getHost() + ":"
379                         + _destination.getAddress().getPort() + uri;
380                 Authorization auth = _destination.getProxyAuthentication();
381                 if (auth != null)
382                     auth.setCredentials(_exchange);
383             }
384 
385             _generator.setRequest(_exchange._method,uri);
386 
387             if (_exchange._version >= HttpVersions.HTTP_1_1_ORDINAL)
388             {
389                 if (!_exchange._requestFields.containsKey(HttpHeaders.HOST_BUFFER))
390                     _exchange._requestFields.add(HttpHeaders.HOST_BUFFER,_destination.getHostHeader());
391             }
392 
393             if (_exchange._requestContent != null)
394             {
395                 _exchange._requestFields.putLongField(HttpHeaders.CONTENT_LENGTH,_exchange._requestContent.length());
396                 _generator.completeHeader(_exchange._requestFields,false);
397                 _generator.addContent(_exchange._requestContent,true);
398             }
399             else if (_exchange._requestContentSource != null)
400             {
401                 _generator.completeHeader(_exchange._requestFields,false);
402                 int available = _exchange._requestContentSource.available();
403                 if (available > 0)
404                 {
405                     // TODO deal with any known content length
406 
407                     // TODO reuse this buffer!
408                     byte[] buf = new byte[available];
409                     int length = _exchange._requestContentSource.read(buf);
410                     _generator.addContent(new ByteArrayBuffer(buf,0,length),false);
411                 }
412             }
413             else
414             {
415                 _exchange._requestFields.remove(HttpHeaders.CONTENT_LENGTH); // TODO
416                 // :
417                 // should
418                 // not
419                 // be
420                 // needed
421                 _generator.completeHeader(_exchange._requestFields,true);
422             }
423 
424             _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
425         }
426     }
427 
428     /* ------------------------------------------------------------ */
429     protected void reset(boolean returnBuffers) throws IOException
430     {
431         _requestComplete = false;
432         _connectionHeader = null;
433         _parser.reset(returnBuffers);
434         _generator.reset(returnBuffers);
435         _http11 = true;
436     }
437 
438     /* ------------------------------------------------------------ */
439     private boolean shouldClose()
440     {
441         if (_connectionHeader!=null)
442         {
443             if (HttpHeaderValues.CLOSE_BUFFER.equals(_connectionHeader))
444                 return true;
445             if (HttpHeaderValues.KEEP_ALIVE_BUFFER.equals(_connectionHeader))
446                 return false;
447         }
448         return !_http11;
449     }
450 
451     /* ------------------------------------------------------------ */
452     private class Handler extends HttpParser.EventHandler
453     {
454         @Override
455         public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
456         {
457             // System.out.println( method.toString() + "///" + url.toString() +
458             // "///" + version.toString() );
459             // TODO validate this is acceptable, the <!DOCTYPE goop was coming
460             // out here
461             // throw new IllegalStateException();
462         }
463 
464         @Override
465         public void startResponse(Buffer version, int status, Buffer reason) throws IOException
466         {
467             _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version);
468             _exchange.getEventListener().onResponseStatus(version,status,reason);
469             _exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS);
470         }
471 
472         @Override
473         public void parsedHeader(Buffer name, Buffer value) throws IOException
474         {
475             if (HttpHeaders.CACHE.getOrdinal(name) == HttpHeaders.CONNECTION_ORDINAL)
476             {
477                 _connectionHeader = HttpHeaderValues.CACHE.lookup(value);
478             }
479             _exchange.getEventListener().onResponseHeader(name,value);
480         }
481 
482         @Override
483         public void headerComplete() throws IOException
484         {
485             _exchange.setStatus(HttpExchange.STATUS_PARSING_CONTENT);
486         }
487 
488         @Override
489         public void content(Buffer ref) throws IOException
490         {
491             _exchange.getEventListener().onResponseContent(ref);
492         }
493 
494         @Override
495         public void messageComplete(long contextLength) throws IOException
496         {
497             _exchange.setStatus(HttpExchange.STATUS_COMPLETED);
498         }
499     }
500 
501     /* ------------------------------------------------------------ */
502     public String toString()
503     {
504         return "HttpConnection@" + hashCode() + "//" + _destination.getAddress().getHost() + ":" + _destination.getAddress().getPort();
505     }
506 
507     /* ------------------------------------------------------------ */
508     public String toDetailString()
509     {
510         return toString() + " ex=" + _exchange + " " + _timeout.getAge();
511     }
512 
513     /* ------------------------------------------------------------ */
514     /**
515      * @return the last
516      */
517     public long getLast()
518     {
519         return _last;
520     }
521 
522     /* ------------------------------------------------------------ */
523     /**
524      * @param last
525      *            the last to set
526      */
527     public void setLast(long last)
528     {
529         _last = last;
530     }
531 
532     /* ------------------------------------------------------------ */
533     public void close() throws IOException
534     {
535         _endp.close();
536     }
537 
538 }