Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | File Members | Related Pages

HttpClient.d

Go to the documentation of this file.
00001 /*******************************************************************************
00002 
00003         @file HttpClient.d
00004         
00005         Copyright (c) 2004 Kris Bell
00006         
00007         This software is provided 'as-is', without any express or implied
00008         warranty. In no event will the authors be held liable for damages
00009         of any kind arising from the use of this software.
00010         
00011         Permission is hereby granted to anyone to use this software for any 
00012         purpose, including commercial applications, and to alter it and/or 
00013         redistribute it freely, subject to the following restrictions:
00014         
00015         1. The origin of this software must not be misrepresented; you must 
00016            not claim that you wrote the original software. If you use this 
00017            software in a product, an acknowledgment within documentation of 
00018            said product would be appreciated but is not required.
00019 
00020         2. Altered source versions must be plainly marked as such, and must 
00021            not be misrepresented as being the original software.
00022 
00023         3. This notice may not be removed or altered from any distribution
00024            of the source.
00025 
00026         4. Derivative works are permitted, but they must carry this notice
00027            in full and credit the original source.
00028 
00029 
00030                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00031 
00032 
00033         @version        Initial version, April 2004      
00034         @author         Kris
00035 
00036 
00037 *******************************************************************************/
00038 
00039 module mango.http.client.HttpClient;
00040 
00041 private import  mango.base.System;
00042 
00043 private import  mango.utils.Text;
00044 
00045 private import  mango.io.Uri,
00046                 mango.io.Token,
00047                 mango.io.Buffer,
00048                 mango.io.SocketConduit,
00049                 mango.io.Exception,
00050                 mango.io.Tokenizer,
00051                 mango.io.DisplayWriter;
00052 
00053 private import  mango.http.server.HttpParams,  
00054                 mango.http.server.HttpHeaders,
00055                 mango.http.server.HttpCookies,
00056                 mango.http.server.HttpResponse;
00057 
00058 /*******************************************************************************
00059 
00060         Supports the basic needs of a client making requests of an HTTP
00061         server. The following is an example of how this might be used:
00062 
00063         @code
00064         // callback for client reader
00065         void sink (char[] content)
00066         {
00067                 Stdout.put (content);
00068         }
00069 
00070         // create client for a GET request
00071         auto HttpClient client = new HttpClient (HttpClient.Get, "http://www.digitalmars.com/d/intro.html");
00072 
00073         // setup a Host header
00074         client.getRequestHeaders.add (HttpHeader.Host, client.getUri.getHost());
00075 
00076         // make request
00077         client.open ();
00078 
00079         // check return status for validity
00080         if (client.isResponseOK)
00081            {
00082            // extract content length (be aware of -1 return, for no header)
00083            int length = client.getResponseHeaders.getInt (HttpHeader.ContentLength);
00084            if (length < 0)
00085                length = int.max;
00086         
00087            // display all returned headers
00088            Stdout.put (client.getResponseHeaders);
00089         
00090            // display remaining content
00091            client.read (&sink, length);
00092            }
00093         else
00094            Stderr.put (client.getResponse);
00095         @endcode
00096 
00097 *******************************************************************************/
00098 
00099 class HttpClient
00100 {       
00101         // this is struct rather than typedef to avoid compiler bugs
00102         private struct RequestMethod
00103         {
00104                 final char[]            name;
00105         }    
00106                         
00107         // class members; there's a surprising amount of stuff here!
00108         private MutableUri              uri;
00109         private IBuffer                 input,
00110                                         output;
00111         private SocketConduit           socket;
00112         private RequestMethod           method;
00113         private InternetAddress         address;
00114         private HttpMutableParams       paramsOut;
00115         private HttpHeaders             headersIn;
00116         private HttpMutableHeaders      headersOut;
00117         private HttpMutableCookies      cookiesOut;
00118         private ResponseLine            responseLine;
00119 
00120         // default to three second timeout on read operations ...
00121         private static final uint DefaultReadTimeout = System.Interval.Second * 3;
00122 
00123         // use HTTP v1.0 ?
00124         private static const char[] DefaultHttpVersion = "HTTP/1.1";
00125 
00126         // standard set of request methods ...
00127         static const RequestMethod      Get = {"GET"},
00128                                         Put = {"PUT"},
00129                                         Head = {"HEAD"},
00130                                         Post = {"POST"},
00131                                         Trace = {"TRACE"},
00132                                         Delete = {"DELETE"},
00133                                         Options = {"OPTIONS"},
00134                                         Connect = {"CONNECT"};
00135 
00136         /***********************************************************************
00137         
00138                 Create a client for the given URL. The argument should be
00139                 fully qualified with an "http:" or "https:" scheme, or an
00140                 explicit port should be provided.
00141 
00142         ***********************************************************************/
00143 
00144         this (RequestMethod method, char[] url)
00145         {
00146                 this (method, new MutableUri(url));
00147         }
00148 
00149         /***********************************************************************
00150         
00151                 Create a client with the provided Uri instance. The Uri should 
00152                 be fully qualified with an "http:" or "https:" scheme, or an
00153                 explicit port should be provided. 
00154 
00155         ***********************************************************************/
00156 
00157         this (RequestMethod method, MutableUri uri)
00158         {
00159                 this.uri = uri;
00160                 this.method = method;
00161 
00162                 responseLine = new ResponseLine ();
00163                 headersIn    = new HttpHeaders  ();
00164 
00165                 paramsOut    = new HttpMutableParams  (new Buffer (1024 * 1));
00166                 headersOut   = new HttpMutableHeaders (new Buffer (1024 * 4));
00167                 cookiesOut   = new HttpMutableCookies (headersOut);
00168 
00169                 // decode the host name (may take a second or two)
00170                 address = new InternetAddress (uri.getHost(), uri.getValidPort());
00171         }
00172 
00173         /***********************************************************************
00174         
00175                 Attempt to clean up when garbage collected
00176 
00177         ***********************************************************************/
00178 
00179         ~this ()
00180         {
00181                 close ();
00182         }
00183 
00184         /***********************************************************************
00185         
00186                 Get the current input headers, as returned by the host request.
00187 
00188         ***********************************************************************/
00189 
00190         HttpHeaders getResponseHeaders()
00191         {
00192                 return headersIn;
00193         }
00194 
00195         /***********************************************************************
00196         
00197                 Gain access to the request headers. Use this to add whatever
00198                 headers are required for a request. 
00199 
00200         ***********************************************************************/
00201 
00202         HttpMutableHeaders getRequestHeaders()
00203         {
00204                 return headersOut;
00205         }
00206 
00207         /***********************************************************************
00208         
00209                 Gain access to the request parameters. Use this to add x=y
00210                 style parameters to the request. These will be appended to
00211                 the request assuming the original Uri does not contain any
00212                 of its own.
00213 
00214         ***********************************************************************/
00215 
00216         HttpMutableParams getRequestParams()
00217         {
00218                 return paramsOut;
00219         }
00220 
00221         /***********************************************************************
00222         
00223                 Return the Uri associated with this client
00224 
00225         ***********************************************************************/
00226 
00227         Uri getUri()
00228         {
00229                 return uri;
00230         }
00231 
00232         /***********************************************************************
00233         
00234                 Return the response-line for the latest request. This takes 
00235                 the form of "version status reason" as defined in the HTTP
00236                 RFC.
00237 
00238         ***********************************************************************/
00239 
00240         ResponseLine getResponse()
00241         {
00242                 return responseLine;
00243         }
00244 
00245         /***********************************************************************
00246         
00247                 Return the HTTP status code set by the remote server
00248 
00249         ***********************************************************************/
00250 
00251         int getStatus()
00252         {
00253                 return responseLine.getStatus();
00254         }
00255 
00256         /***********************************************************************
00257         
00258                 Return whether the response was OK or not
00259 
00260         ***********************************************************************/
00261 
00262         bool isResponseOK()
00263         {
00264                 return getStatus() == HttpResponseCode.OK;
00265         }
00266 
00267         /***********************************************************************
00268         
00269                 Add a cookie to the outgoing headers
00270 
00271         ***********************************************************************/
00272 
00273         void addCookie (Cookie cookie)
00274         {
00275                 cookiesOut.add (cookie);
00276         }
00277 
00278         /***********************************************************************
00279         
00280                 Close all resources used by a request. You must invoke this 
00281                 between successive open() calls.
00282 
00283         ***********************************************************************/
00284 
00285         void close ()
00286         {
00287                 if (socket)
00288                    {
00289                    socket.shutdown ();
00290                    socket.close ();
00291                    socket = null;
00292                    }
00293         }
00294 
00295         /***********************************************************************
00296 
00297                 Reset the client such that it is ready for a new request.
00298         
00299         ***********************************************************************/
00300 
00301         void reset ()
00302         {
00303                 headersIn.reset();
00304                 headersOut.reset();
00305                 paramsOut.reset();
00306         }
00307 
00308         /***********************************************************************
00309 
00310                 Overridable method to create a Socket. You may find a need 
00311                 to override this for some purpose; perhaps to add input or 
00312                 output filters.
00313                  
00314         ***********************************************************************/
00315 
00316         protected SocketConduit createSocket ()
00317         {
00318                 return new SocketConduit;
00319         }
00320 
00321         /***********************************************************************
00322         
00323                 Make a request for the resource specified via the constructor,
00324                 using a callback for pumping additional data to the host. This 
00325                 defaults to a three-second timeout period. The return value 
00326                 represents the input buffer, from which all returned headers 
00327                 and content may be accessed.
00328                 
00329         ***********************************************************************/
00330 
00331         IBuffer open (IWritable pump)
00332         {
00333                 return open (DefaultReadTimeout, pump);
00334         }
00335 
00336         /***********************************************************************
00337         
00338                 Make a request for the resource specified via the constructor,
00339                 using the specified timeout period (in milli-seconds).The 
00340                 return value represents the input buffer, from which all
00341                 returned headers and content may be accessed.
00342                 
00343         ***********************************************************************/
00344 
00345         IBuffer open (uint timeout = DefaultReadTimeout)
00346         {
00347                 return open (timeout, null);
00348         }
00349 
00350         /***********************************************************************
00351         
00352                 Make a request for the resource specified via the constructor
00353                 using the specified timeout period (in micro-seconds), and a
00354                 user-defined callback for pumping additional data to the host.
00355                 The callback would be used when uploading data during a 'put'
00356                 operation (or equivalent). The return value represents the 
00357                 input buffer, from which all returned headers and content may 
00358                 be accessed.
00359                 
00360         ***********************************************************************/
00361 
00362         IBuffer open (uint timeout, IWritable pump)
00363         {
00364                 IWriter writer;
00365                 
00366                 // create socket, and connect it 
00367                 socket = createSocket;
00368                 socket.setTimeout (timeout);
00369                 socket.connect (address);
00370                 
00371                 // create buffers for input and output
00372                 input = socket.createBuffer;
00373                 output = socket.createBuffer;
00374                   
00375                 // bind a writer to the socket
00376                 writer = new DisplayWriter (output);
00377 
00378                 // attach query parameters if user has added some
00379                 char[] query = paramsOut.toOutputString;
00380                 if (query.length)
00381                     uri.setQuery (query);
00382 
00383                 // format request 
00384                 writer.put (method.name)
00385                       .put (' ')
00386                       .put (uri.getPath)
00387                       .put (' ')
00388                       .put (DefaultHttpVersion)
00389                       .cr  ()
00390                       .put (headersOut)
00391                       .cr  ();
00392 
00393                 // user has more data to send?
00394                 if (pump)
00395                     pump.write (writer);
00396                 
00397                 // send entire request
00398                 writer.flush ();
00399 
00400                 // Token for initial parsing of input header lines
00401                 CompositeToken line = new CompositeToken (Tokenizers.line, input);
00402 
00403                 // skip any blank lines
00404                 while (line.next() && line.getLength() == 0) 
00405                       {}
00406 
00407                 // is this a bogus request?
00408                 if (input.readable == 0)
00409                     throw new IOException ("truncated response");
00410                     
00411                 // read response line
00412                 responseLine.parse (line.toString);
00413 
00414                 // parse headers and go home
00415                 headersIn.parse (input);
00416                 return input;
00417         }
00418 
00419         /***********************************************************************
00420         
00421         ***********************************************************************/
00422 
00423         void read (void delegate (char[]) sink, long length = long.max)
00424         {
00425                 // display remaining content
00426                 do {
00427                    length -= input.readable;
00428                    sink (input.toString);
00429                    input.clear ();
00430                    } while (length > 0 && socket.read(input) != socket.Eof);
00431         }
00432 }
00433 
00434 
00435 /******************************************************************************
00436 
00437         Class to represent an HTTP response-line
00438 
00439 ******************************************************************************/
00440 
00441 private class ResponseLine : IWritable
00442 {
00443         private Buffer          buf;
00444         private BoundToken      token;
00445         private char[]          reason,
00446                                 status,
00447                                 vershion;
00448 
00449         /**********************************************************************
00450 
00451                 Construct response-line with pre-allocated buffer & token
00452 
00453         **********************************************************************/
00454 
00455         this ()
00456         {
00457                 buf = new Buffer;
00458 
00459                 // bind tokens to a space-tokenizer
00460                 token = new BoundToken (new SpaceTokenizer());
00461         }
00462 
00463         /**********************************************************************
00464 
00465                 Parse the the given response-line into its constituent 
00466                 components.
00467 
00468         **********************************************************************/
00469 
00470         void parse (char[] line)
00471         {
00472                 // setup our buffer with the request
00473                 buf.setValidContent (line);
00474 
00475                 if (token.next (buf))
00476                    {
00477                    vershion = token.toString();
00478                    if (token.next (buf))
00479                       {
00480                       status = token.toString();
00481                       reason = line[buf.getPosition..line.length];
00482                       return;
00483                       }
00484                    }
00485                throw new IOException ("Invalid HTTP response-line: '"~line~"'");
00486         }
00487 
00488         /**********************************************************************
00489 
00490                 Return HTTP version
00491 
00492         **********************************************************************/
00493 
00494         char[] getVersion ()
00495         {
00496                 return vershion;
00497         }
00498 
00499         /**********************************************************************
00500 
00501                 Return reason text
00502 
00503         **********************************************************************/
00504 
00505         char[] getReason ()
00506         {
00507                 return reason;
00508         }
00509 
00510         /**********************************************************************
00511 
00512                 Return status integer
00513 
00514         **********************************************************************/
00515 
00516         int getStatus ()
00517         {
00518                 return Text.atoi(status);
00519         }
00520 
00521         /**********************************************************************
00522 
00523                 convert back to original string
00524 
00525         **********************************************************************/
00526 
00527         override char[] toString ()
00528         {
00529                 return vershion~" "~status~" "~reason;
00530         }
00531 
00532         /**********************************************************************
00533 
00534                 Output the string via the given writer
00535 
00536         **********************************************************************/
00537 
00538         void write (IWriter writer)
00539         {
00540                writer.put(toString).cr();
00541         }
00542 }
00543 
00544 

Generated on Tue Jan 25 21:18:21 2005 for Mango by doxygen 1.3.6