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