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