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