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.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 writer;
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                 writer = new DisplayWriter (output);
00379 
00380                 // attach query parameters if user has added some
00381                 char[] query = paramsOut.toOutputString;
00382                 if (query.length)
00383                     uri.setQuery (query);
00384 
00385                 // format request 
00386                 writer.put (method.name)
00387                       .put (' ')
00388                       .put (uri.getPath)
00389                       .put (' ')
00390                       .put (DefaultHttpVersion)
00391                       .cr  ()
00392                       .put (headersOut)
00393                       .cr  ();
00394 
00395                 // user has more data to send?
00396                 if (pump)
00397                     pump.write (writer);
00398                 
00399                 // send entire request
00400                 writer.flush ();
00401 
00402                 // Token for initial parsing of input header lines
00403                 CompositeToken line = new CompositeToken (Tokenizers.line, input);
00404 
00405                 // skip any blank lines
00406                 while (line.next() && line.getLength() == 0) 
00407                       {}
00408 
00409                 // is this a bogus request?
00410                 if (input.readable == 0)
00411                     throw new IOException ("truncated response");
00412                     
00413                 // read response line
00414                 responseLine.parse (line.toString);
00415 
00416                 // parse headers and go home
00417                 headersIn.parse (input);
00418                 return input;
00419         }
00420 
00421         /***********************************************************************
00422         
00423         ***********************************************************************/
00424 
00425         void read (void delegate (char[]) sink, long length = long.max)
00426         {
00427                 // display remaining content
00428                 do {
00429                    length -= input.readable;
00430                    sink (input.toString);
00431                    input.clear ();
00432                    } while (length > 0 && socket.read(input) != socket.Eof);
00433         }
00434 }
00435 
00436 
00437 /******************************************************************************
00438 
00439         Class to represent an HTTP response-line
00440 
00441 ******************************************************************************/
00442 
00443 private class ResponseLine : IWritable
00444 {
00445         private Buffer          buf;
00446         private BoundToken      token;
00447         private char[]          reason,
00448                                 status,
00449                                 vershion;
00450 
00451         /**********************************************************************
00452 
00453                 Construct response-line with pre-allocated buffer & token
00454 
00455         **********************************************************************/
00456 
00457         this ()
00458         {
00459                 buf = new Buffer;
00460 
00461                 // bind tokens to a space-tokenizer
00462                 token = new BoundToken (new SpaceTokenizer());
00463         }
00464 
00465         /**********************************************************************
00466 
00467                 Parse the the given response-line into its constituent 
00468                 components.
00469 
00470         **********************************************************************/
00471 
00472         void parse (char[] line)
00473         {
00474                 // setup our buffer with the request
00475                 buf.setValidContent (line);
00476 
00477                 if (token.next (buf))
00478                    {
00479                    vershion = token.toString();
00480                    if (token.next (buf))
00481                       {
00482                       status = token.toString();
00483                       reason = line[buf.getPosition..line.length];
00484                       return;
00485                       }
00486                    }
00487                throw new IOException ("Invalid HTTP response-line: '"~line~"'");
00488         }
00489 
00490         /**********************************************************************
00491 
00492                 Return HTTP version
00493 
00494         **********************************************************************/
00495 
00496         char[] getVersion ()
00497         {
00498                 return vershion;
00499         }
00500 
00501         /**********************************************************************
00502 
00503                 Return reason text
00504 
00505         **********************************************************************/
00506 
00507         char[] getReason ()
00508         {
00509                 return reason;
00510         }
00511 
00512         /**********************************************************************
00513 
00514                 Return status integer
00515 
00516         **********************************************************************/
00517 
00518         int getStatus ()
00519         {
00520                 return Int.parse (status);
00521         }
00522 
00523         /**********************************************************************
00524 
00525                 convert back to original string
00526 
00527         **********************************************************************/
00528 
00529         override char[] toString ()
00530         {
00531                 return vershion~" "~status~" "~reason;
00532         }
00533 
00534         /**********************************************************************
00535 
00536                 Output the string via the given writer
00537 
00538         **********************************************************************/
00539 
00540         void write (IWriter writer)
00541         {
00542                writer.put(toString).cr();
00543         }
00544 }
00545 
00546 

Generated on Sat Apr 9 20:11:26 2005 for Mango by doxygen 1.3.6