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

Generated on Sun Nov 7 19:06:51 2004 for Mango by doxygen 1.3.6