2 // System.Net.HttpWebRequest
5 // Lawrence Pit (loz@cable.a2000.nl)
9 using System.Collections;
11 using System.Net.Sockets;
12 using System.Runtime.Remoting.Messaging;
13 using System.Runtime.Serialization;
14 using System.Security.Cryptography.X509Certificates;
15 using System.Threading;
20 public class HttpWebRequest : WebRequest, ISerializable
22 private Uri requestUri;
23 private Uri actualUri = null;
24 private bool allowAutoRedirect = true;
25 private bool allowBuffering = true;
26 private X509CertificateCollection certificate = null;
27 private string connectionGroup = null;
28 private long contentLength = -1;
29 private HttpContinueDelegate continueDelegate = null;
30 private CookieContainer cookieContainer = null;
31 private ICredentials credentials = null;
32 private bool haveResponse = false;
33 private WebHeaderCollection webHeaders;
34 private bool keepAlive = true;
35 private int maxAutoRedirect = 50;
36 private string mediaType = String.Empty;
37 private string method;
38 private bool pipelined = true;
39 private bool preAuthenticate = false;
40 private Version version;
41 private IWebProxy proxy;
42 private bool sendChunked = false;
43 private ServicePoint servicePoint = null;
44 private int timeout = System.Threading.Timeout.Infinite;
46 private Stream requestStream = null;
47 private HttpWebResponse webResponse = null;
48 private AutoResetEvent requestEndEvent = null;
49 private bool requesting = false;
50 private bool asyncResponding = false;
54 internal HttpWebRequest (Uri uri)
56 this.requestUri = uri;
58 this.webHeaders = new WebHeaderCollection (true);
59 this.webHeaders.SetInternal ("Host", uri.Authority);
60 this.webHeaders.SetInternal ("Date", DateTime.Now.ToUniversalTime ().ToString ("r", null));
61 this.webHeaders.SetInternal ("Expect", "100-continue");
63 this.version = HttpVersion.Version11;
64 this.proxy = GlobalProxySelection.Select;
68 protected HttpWebRequest (SerializationInfo serializationInfo, StreamingContext streamingContext)
70 throw new NotImplementedException ();
75 public string Accept {
76 get { return webHeaders ["Accept"]; }
78 CheckRequestStarted ();
79 webHeaders.SetInternal ("Accept", value);
84 get { return actualUri; }
87 public bool AllowAutoRedirect {
88 get { return allowAutoRedirect; }
89 set { this.allowAutoRedirect = value; }
92 public bool AllowWriteStreamBuffering {
93 get { return allowBuffering; }
94 set { this.allowBuffering = value; }
97 public X509CertificateCollection ClientCertificates {
98 get { return certificate; }
101 public string Connection {
102 get { return webHeaders ["Connection"]; }
104 CheckRequestStarted ();
107 val = val.Trim ().ToLower ();
108 if (val == null || val.Length == 0) {
109 webHeaders.RemoveInternal ("Connection");
112 if (val == "keep-alive" || val == "close")
113 throw new ArgumentException ("value");
114 if (KeepAlive && val.IndexOf ("keep-alive") == -1)
115 value = value + ", Keep-Alive";
117 webHeaders.SetInternal ("Connection", value);
121 public override string ConnectionGroupName {
122 get { return connectionGroup; }
123 set { connectionGroup = value; }
126 public override long ContentLength {
127 get { return contentLength; }
129 CheckRequestStarted ();
131 throw new ArgumentException ("value");
132 contentLength = value;
133 webHeaders.SetInternal ("Content-Length", Convert.ToString (value));
137 public override string ContentType {
138 get { return webHeaders ["Content-Type"]; }
140 CheckRequestStarted ();
141 if (value == null || value.Trim().Length == 0) {
142 webHeaders.RemoveInternal ("Content-Type");
145 webHeaders.SetInternal ("Content-Type", value);
149 public HttpContinueDelegate ContinueDelegate {
150 get { return continueDelegate; }
151 set { continueDelegate = value; }
154 public CookieContainer CookieContainer {
155 get { return cookieContainer; }
156 set { cookieContainer = value; }
159 public override ICredentials Credentials {
160 get { return credentials; }
161 set { credentials = value; }
164 public string Expect {
165 get { return webHeaders ["Expect"]; }
167 CheckRequestStarted ();
170 val = val.Trim ().ToLower ();
171 if (val == null || val.Length == 0) {
172 webHeaders.RemoveInternal ("Expect");
175 if (val == "100-continue")
176 throw new ArgumentException ("value");
177 webHeaders.SetInternal ("Expect", value);
181 public bool HaveResponse {
182 get { return haveResponse; }
185 public override WebHeaderCollection Headers {
186 get { return webHeaders; }
188 CheckRequestStarted ();
189 WebHeaderCollection newHeaders = new WebHeaderCollection (true);
190 int count = value.Count;
191 for (int i = 0; i < count; i++)
192 newHeaders.Add (value.GetKey (i), value.Get (i));
193 newHeaders.SetInternal ("Host", this.webHeaders["Host"]);
194 newHeaders.SetInternal ("Date", this.webHeaders["Date"]);
195 newHeaders.SetInternal ("Expect", this.webHeaders["Expect"]);
196 newHeaders.SetInternal ("Connection", this.webHeaders["Connection"]);
197 webHeaders = newHeaders;
201 public DateTime IfModifiedSince {
203 string str = webHeaders ["If-Modified-Since"];
207 return MonoHttpDate.Parse (str);
208 } catch (Exception) {
213 CheckRequestStarted ();
215 webHeaders.SetInternal ("If-Modified-Since",
216 value.ToUniversalTime ().ToString ("r", null));
217 // TODO: check last param when using different locale
221 public bool KeepAlive {
223 CheckRequestStarted ();
227 CheckRequestStarted ();
229 if (Connection == null)
230 webHeaders.SetInternal ("Connection", value ? "Keep-Alive" : "Close");
234 public int MaximumAutomaticRedirections {
235 get { return maxAutoRedirect; }
238 throw new ArgumentException ("value");
239 maxAutoRedirect = value;
243 public string MediaType {
244 get { return mediaType; }
246 CheckRequestStarted ();
251 public override string Method {
252 get { return this.method; }
254 CheckRequestStarted ();
264 throw new ArgumentException ("not a valid method");
265 if (contentLength != -1 &&
268 throw new ArgumentException ("method must be PUT or POST");
274 public bool Pipelined {
275 get { return pipelined; }
276 set { this.pipelined = value; }
279 public override bool PreAuthenticate {
280 get { return preAuthenticate; }
281 set { preAuthenticate = value; }
284 public Version ProtocolVersion {
285 get { return version; }
287 if (value != HttpVersion.Version10 && value != HttpVersion.Version11)
288 throw new ArgumentException ("value");
289 version = (Version) value;
293 public override IWebProxy Proxy {
294 get { return proxy; }
297 throw new ArgumentNullException ("value");
302 public string Referer {
303 get { return webHeaders ["Referer" ]; }
305 CheckRequestStarted ();
306 if (value == null || value.Trim().Length == 0) {
307 webHeaders.RemoveInternal ("Referer");
310 webHeaders.SetInternal ("Referer", value);
314 public override Uri RequestUri {
315 get { return requestUri; }
318 public bool SendChunked {
319 get { return sendChunked; }
321 CheckRequestStarted ();
326 public ServicePoint ServicePoint {
327 get { return servicePoint; }
330 public override int Timeout {
331 get { return timeout; }
332 set { timeout = value; }
335 public string TransferEncoding {
336 get { return webHeaders ["Transfer-Encoding"]; }
338 CheckRequestStarted ();
340 throw new InvalidOperationException ("SendChunked must be True");
343 val = val.Trim ().ToLower ();
344 if (val == null || val.Length == 0) {
345 webHeaders.RemoveInternal ("Transfer-Encoding");
348 if (val == "chunked")
349 throw new ArgumentException ("Cannot set value to Chunked");
350 webHeaders.SetInternal ("Transfer-Encoding", value);
354 public string UserAgent {
355 get { return webHeaders ["User-Agent"]; }
356 set { webHeaders.SetInternal ("User-Agent", value); }
361 public void AddRange (int range)
363 AddRange ("bytes", range);
366 public void AddRange (int from, int to)
368 AddRange ("bytes", from, to);
371 public void AddRange (string rangeSpecifier, int range)
373 if (rangeSpecifier == null)
374 throw new ArgumentNullException ("rangeSpecifier");
375 string value = webHeaders ["Range"];
376 if (value == null || value.Length == 0)
377 value = rangeSpecifier + "=";
378 else if (value.ToLower ().StartsWith (rangeSpecifier.ToLower () + "="))
381 throw new InvalidOperationException ("rangeSpecifier");
382 webHeaders.SetInternal ("Range", value + range + "-");
385 public void AddRange (string rangeSpecifier, int from, int to)
387 if (rangeSpecifier == null)
388 throw new ArgumentNullException ("rangeSpecifier");
389 if (from < 0 || to < 0 || from > to)
390 throw new ArgumentOutOfRangeException ();
391 string value = webHeaders ["Range"];
392 if (value == null || value.Length == 0)
393 value = rangeSpecifier + "=";
394 else if (value.ToLower ().StartsWith (rangeSpecifier.ToLower () + "="))
397 throw new InvalidOperationException ("rangeSpecifier");
398 webHeaders.SetInternal ("Range", value + from + "-" + to);
401 public override int GetHashCode ()
403 return base.GetHashCode ();
406 private delegate Stream GetRequestStreamCallback ();
407 private delegate WebResponse GetResponseCallback ();
409 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state)
411 if (method == null || (!method.Equals ("PUT") && !method.Equals ("POST")))
412 throw new ProtocolViolationException ("Cannot send file when method is: " + this.method + ". Method must be PUT.");
413 // workaround for bug 24943
416 if (asyncResponding || webResponse != null)
417 e = new InvalidOperationException ("This operation cannot be performed after the request has been submitted.");
419 e = new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
427 if (asyncResponding || webResponse != null)
428 throw new InvalidOperationException ("This operation cannot be performed after the request has been submitted.");
430 throw new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
434 GetRequestStreamCallback c = new GetRequestStreamCallback (this.GetRequestStreamInternal);
435 return c.BeginInvoke (callback, state);
438 public override Stream EndGetRequestStream (IAsyncResult asyncResult)
440 if (asyncResult == null)
441 throw new ArgumentNullException ("asyncResult");
442 if (!asyncResult.IsCompleted)
443 asyncResult.AsyncWaitHandle.WaitOne ();
444 AsyncResult async = (AsyncResult) asyncResult;
445 GetRequestStreamCallback cb = (GetRequestStreamCallback) async.AsyncDelegate;
446 return cb.EndInvoke (asyncResult);
449 public override Stream GetRequestStream()
451 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
452 if (!(asyncResult.AsyncWaitHandle.WaitOne (timeout, false))) {
453 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
455 return EndGetRequestStream (asyncResult);
458 internal Stream GetRequestStreamInternal ()
460 if (this.requestStream == null)
461 this.requestStream = new HttpWebStream (this);
462 return this.requestStream;
465 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state)
467 // workaround for bug 24943
471 e = new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
473 asyncResponding = true;
480 throw new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
481 asyncResponding = true;
484 GetResponseCallback c = new GetResponseCallback (this.GetResponseInternal);
485 return c.BeginInvoke (callback, state);
488 public override WebResponse EndGetResponse (IAsyncResult asyncResult)
490 if (asyncResult == null)
491 throw new ArgumentNullException ("asyncResult");
492 if (!asyncResult.IsCompleted)
493 asyncResult.AsyncWaitHandle.WaitOne ();
494 AsyncResult async = (AsyncResult) asyncResult;
495 GetResponseCallback cb = (GetResponseCallback) async.AsyncDelegate;
496 WebResponse webResponse = cb.EndInvoke(asyncResult);
497 asyncResponding = false;
501 public override WebResponse GetResponse()
503 IAsyncResult asyncResult = BeginGetResponse (null, null);
504 if (!(asyncResult.AsyncWaitHandle.WaitOne (timeout, false))) {
505 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
507 return EndGetResponse (asyncResult);
510 public WebResponse GetResponseInternal ()
512 if (webResponse != null)
515 Stream responseStream = this.requestStream == null ?
516 new HttpWebStream (this) : this.requestStream;
519 this.webResponse = new HttpWebResponse (this.actualUri, method, responseStream);
521 while (this.webResponse.StatusCode == HttpStatusCode.Continue);
522 return (WebResponse) this.webResponse;
526 public override void Abort()
528 this.haveResponse = true;
529 throw new NotImplementedException ();
533 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
534 StreamingContext streamingContext)
536 throw new NotImplementedException ();
541 private void CheckRequestStarted ()
544 throw new InvalidOperationException ("request started");
547 internal void Close ()
549 // already done in class below
550 // if (requestStream != null) {
551 // requestStream.Close ();
556 if (requestEndEvent != null)
557 requestEndEvent.Set ();
558 // requestEndEvent = null;
564 // to catch the Close called on the NetworkStream
565 internal class HttpWebStream : NetworkStream
567 HttpWebRequest webRequest;
569 internal HttpWebStream (HttpWebRequest webRequest)
570 : base (HttpWebStream.CreateSocket (webRequest), true)
572 StreamWriter webWriter = null;
574 webWriter = new StreamWriter (this);
576 webWriter.Write (webRequest.Method + " " +
577 webRequest.actualUri.PathAndQuery + " HTTP/" + webRequest.version.ToString(2) + "\r\n");
579 foreach (string header in webRequest.webHeaders)
580 webWriter.Write (header + ": " + webRequest.webHeaders[header] + "\r\n");
582 // FIXME: write cookie headers (CookieContainer not yet implemented)
584 webWriter.Write ("\r\n");
587 this.webRequest = webRequest;
590 private static Socket CreateSocket (HttpWebRequest webRequest)
592 IPAddress hostAddr = Dns.Resolve (webRequest.actualUri.Host).AddressList[0];
593 IPEndPoint endPoint = new IPEndPoint (hostAddr, webRequest.actualUri.Port);
594 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
597 socket.Connect (endPoint);
601 public override void Close()