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

Generated on Fri May 27 18:11:56 2005 for Mango by  doxygen 1.4.0