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