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