2 // System.Net.HttpWebRequest
5 // Lawrence Pit (loz@cable.a2000.nl)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (c) 2002 Lawrence Pit
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
13 using System.Collections;
15 using System.Net.Sockets;
16 using System.Runtime.Remoting.Messaging;
17 using System.Runtime.Serialization;
18 using System.Security.Cryptography.X509Certificates;
20 using System.Threading;
25 public class HttpWebRequest : WebRequest, ISerializable, IDisposable
27 private Uri requestUri;
28 private Uri actualUri = null;
29 private bool allowAutoRedirect = true;
30 private bool allowBuffering = true;
31 private X509CertificateCollection certificate = null;
32 private string connectionGroup = null;
33 private long contentLength = -1;
34 private HttpContinueDelegate continueDelegate = null;
35 private CookieContainer cookieContainer = null;
36 private ICredentials credentials = null;
37 private bool haveResponse = false;
38 private WebHeaderCollection webHeaders;
39 private bool keepAlive = true;
40 private int maxAutoRedirect = 50;
41 private string mediaType = String.Empty;
42 private string method;
43 private bool pipelined = true;
44 private bool preAuthenticate = false;
45 private Version version;
46 private IWebProxy proxy;
47 private bool sendChunked = false;
48 private ServicePoint servicePoint = null;
49 private int timeout = System.Threading.Timeout.Infinite;
51 private HttpWebRequestStream requestStream = null;
52 private HttpWebResponse webResponse = null;
53 private AutoResetEvent requestEndEvent = null;
54 private bool requesting = false;
55 private bool asyncResponding = false;
56 private Socket socket;
57 private bool requestClosed = false;
58 private bool responseClosed = false;
59 private bool disposed;
63 internal HttpWebRequest (Uri uri)
65 this.requestUri = uri;
67 this.webHeaders = new WebHeaderCollection (true);
68 this.webHeaders.SetInternal ("Host", uri.Authority);
69 this.webHeaders.SetInternal ("Date", DateTime.Now.ToUniversalTime ().ToString ("r", null));
70 this.webHeaders.SetInternal ("Expect", "100-continue");
72 this.version = HttpVersion.Version11;
73 this.proxy = GlobalProxySelection.Select;
77 protected HttpWebRequest (SerializationInfo serializationInfo, StreamingContext streamingContext)
79 throw new NotImplementedException ();
84 public string Accept {
85 get { return webHeaders ["Accept"]; }
87 CheckRequestStarted ();
88 webHeaders.SetInternal ("Accept", value);
93 get { return actualUri; }
96 public bool AllowAutoRedirect {
97 get { return allowAutoRedirect; }
98 set { this.allowAutoRedirect = value; }
101 public bool AllowWriteStreamBuffering {
102 get { return allowBuffering; }
103 set { this.allowBuffering = value; }
106 public X509CertificateCollection ClientCertificates {
107 get { return certificate; }
110 public string Connection {
111 get { return webHeaders ["Connection"]; }
113 CheckRequestStarted ();
116 val = val.Trim ().ToLower ();
117 if (val == null || val.Length == 0) {
118 webHeaders.RemoveInternal ("Connection");
121 if (val == "keep-alive" || val == "close")
122 throw new ArgumentException ("value");
123 if (KeepAlive && val.IndexOf ("keep-alive") == -1)
124 value = value + ", Keep-Alive";
126 webHeaders.SetInternal ("Connection", value);
130 public override string ConnectionGroupName {
131 get { return connectionGroup; }
132 set { connectionGroup = value; }
135 public override long ContentLength {
136 get { return contentLength; }
138 CheckRequestStarted ();
140 throw new ArgumentException ("value");
141 contentLength = value;
142 webHeaders.SetInternal ("Content-Length", Convert.ToString (value));
146 public override string ContentType {
147 get { return webHeaders ["Content-Type"]; }
149 CheckRequestStarted ();
150 if (value == null || value.Trim().Length == 0) {
151 webHeaders.RemoveInternal ("Content-Type");
154 webHeaders.SetInternal ("Content-Type", value);
158 public HttpContinueDelegate ContinueDelegate {
159 get { return continueDelegate; }
160 set { continueDelegate = value; }
163 public CookieContainer CookieContainer {
164 get { return cookieContainer; }
165 set { cookieContainer = value; }
168 public override ICredentials Credentials {
169 get { return credentials; }
170 set { credentials = value; }
173 public string Expect {
174 get { return webHeaders ["Expect"]; }
176 CheckRequestStarted ();
179 val = val.Trim ().ToLower ();
180 if (val == null || val.Length == 0) {
181 webHeaders.RemoveInternal ("Expect");
184 if (val == "100-continue")
185 throw new ArgumentException ("value");
186 webHeaders.SetInternal ("Expect", value);
190 public bool HaveResponse {
191 get { return haveResponse; }
194 public override WebHeaderCollection Headers {
195 get { return webHeaders; }
197 CheckRequestStarted ();
198 WebHeaderCollection newHeaders = new WebHeaderCollection (true);
199 int count = value.Count;
200 for (int i = 0; i < count; i++)
201 newHeaders.Add (value.GetKey (i), value.Get (i));
202 newHeaders.SetInternal ("Host", this.webHeaders["Host"]);
203 newHeaders.SetInternal ("Date", this.webHeaders["Date"]);
204 newHeaders.SetInternal ("Expect", this.webHeaders["Expect"]);
205 newHeaders.SetInternal ("Connection", this.webHeaders["Connection"]);
206 webHeaders = newHeaders;
210 public DateTime IfModifiedSince {
212 string str = webHeaders ["If-Modified-Since"];
216 return MonoHttpDate.Parse (str);
217 } catch (Exception) {
222 CheckRequestStarted ();
224 webHeaders.SetInternal ("If-Modified-Since",
225 value.ToUniversalTime ().ToString ("r", null));
226 // TODO: check last param when using different locale
230 public bool KeepAlive {
232 CheckRequestStarted ();
236 CheckRequestStarted ();
238 if (Connection == null)
239 webHeaders.SetInternal ("Connection", value ? "Keep-Alive" : "Close");
243 public int MaximumAutomaticRedirections {
244 get { return maxAutoRedirect; }
247 throw new ArgumentException ("value");
248 maxAutoRedirect = value;
252 public string MediaType {
253 get { return mediaType; }
255 CheckRequestStarted ();
260 public override string Method {
261 get { return this.method; }
263 CheckRequestStarted ();
273 throw new ArgumentException ("not a valid method");
274 if (contentLength != -1 &&
277 throw new ArgumentException ("method must be PUT or POST");
283 public bool Pipelined {
284 get { return pipelined; }
285 set { this.pipelined = value; }
288 public override bool PreAuthenticate {
289 get { return preAuthenticate; }
290 set { preAuthenticate = value; }
293 public Version ProtocolVersion {
294 get { return version; }
296 if (value != HttpVersion.Version10 && value != HttpVersion.Version11)
297 throw new ArgumentException ("value");
298 version = (Version) value;
302 public override IWebProxy Proxy {
303 get { return proxy; }
306 throw new ArgumentNullException ("value");
311 public string Referer {
312 get { return webHeaders ["Referer" ]; }
314 CheckRequestStarted ();
315 if (value == null || value.Trim().Length == 0) {
316 webHeaders.RemoveInternal ("Referer");
319 webHeaders.SetInternal ("Referer", value);
323 public override Uri RequestUri {
324 get { return requestUri; }
327 public bool SendChunked {
328 get { return sendChunked; }
330 CheckRequestStarted ();
335 public ServicePoint ServicePoint {
336 get { return servicePoint; }
339 public override int Timeout {
340 get { return timeout; }
341 set { timeout = value; }
344 public string TransferEncoding {
345 get { return webHeaders ["Transfer-Encoding"]; }
347 CheckRequestStarted ();
349 throw new InvalidOperationException ("SendChunked must be True");
352 val = val.Trim ().ToLower ();
353 if (val == null || val.Length == 0) {
354 webHeaders.RemoveInternal ("Transfer-Encoding");
357 if (val == "chunked")
358 throw new ArgumentException ("Cannot set value to Chunked");
359 webHeaders.SetInternal ("Transfer-Encoding", value);
363 public string UserAgent {
364 get { return webHeaders ["User-Agent"]; }
365 set { webHeaders.SetInternal ("User-Agent", value); }
370 public void AddRange (int range)
372 AddRange ("bytes", range);
375 public void AddRange (int from, int to)
377 AddRange ("bytes", from, to);
380 public void AddRange (string rangeSpecifier, int range)
382 if (rangeSpecifier == null)
383 throw new ArgumentNullException ("rangeSpecifier");
384 string value = webHeaders ["Range"];
385 if (value == null || value.Length == 0)
386 value = rangeSpecifier + "=";
387 else if (value.ToLower ().StartsWith (rangeSpecifier.ToLower () + "="))
390 throw new InvalidOperationException ("rangeSpecifier");
391 webHeaders.SetInternal ("Range", value + range + "-");
394 public void AddRange (string rangeSpecifier, int from, int to)
396 if (rangeSpecifier == null)
397 throw new ArgumentNullException ("rangeSpecifier");
398 if (from < 0 || to < 0 || from > to)
399 throw new ArgumentOutOfRangeException ();
400 string value = webHeaders ["Range"];
401 if (value == null || value.Length == 0)
402 value = rangeSpecifier + "=";
403 else if (value.ToLower ().StartsWith (rangeSpecifier.ToLower () + "="))
406 throw new InvalidOperationException ("rangeSpecifier");
407 webHeaders.SetInternal ("Range", value + from + "-" + to);
410 public override int GetHashCode ()
412 return base.GetHashCode ();
415 private delegate Stream GetRequestStreamCallback ();
416 private delegate WebResponse GetResponseCallback ();
418 public override IAsyncResult BeginGetRequestStream (AsyncCallback callback, object state)
420 if (method == null || (!method.Equals ("PUT") && !method.Equals ("POST")))
421 throw new ProtocolViolationException ("Cannot send file when method is: " + this.method + ". Method must be PUT.");
422 // workaround for bug 24943
425 if (asyncResponding || webResponse != null)
426 e = new InvalidOperationException ("This operation cannot be performed after the request has been submitted.");
428 e = new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
436 if (asyncResponding || webResponse != null)
437 throw new InvalidOperationException ("This operation cannot be performed after the request has been submitted.");
439 throw new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
443 GetRequestStreamCallback c = new GetRequestStreamCallback (this.GetRequestStreamInternal);
444 return c.BeginInvoke (callback, state);
447 public override Stream EndGetRequestStream (IAsyncResult asyncResult)
449 if (asyncResult == null)
450 throw new ArgumentNullException ("asyncResult");
451 if (!asyncResult.IsCompleted)
452 asyncResult.AsyncWaitHandle.WaitOne ();
453 AsyncResult async = (AsyncResult) asyncResult;
454 GetRequestStreamCallback cb = (GetRequestStreamCallback) async.AsyncDelegate;
455 return cb.EndInvoke (asyncResult);
458 public override Stream GetRequestStream()
460 IAsyncResult asyncResult = BeginGetRequestStream (null, null);
461 if (!(asyncResult.AsyncWaitHandle.WaitOne (timeout, false))) {
462 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
464 return EndGetRequestStream (asyncResult);
467 internal Stream GetRequestStreamInternal ()
469 if (this.requestStream == null) {
470 this.requestStream = new HttpWebRequestStream (this,
472 new EventHandler (OnClose));
475 return this.requestStream;
478 public override IAsyncResult BeginGetResponse (AsyncCallback callback, object state)
480 // workaround for bug 24943
484 e = new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
486 asyncResponding = true;
493 throw new InvalidOperationException ("Cannot re-call start of asynchronous method while a previous call is still in progress.");
494 asyncResponding = true;
497 GetResponseCallback c = new GetResponseCallback (this.GetResponseInternal);
498 return c.BeginInvoke (callback, state);
501 public override WebResponse EndGetResponse (IAsyncResult asyncResult)
503 if (asyncResult == null)
504 throw new ArgumentNullException ("asyncResult");
505 if (!asyncResult.IsCompleted)
506 asyncResult.AsyncWaitHandle.WaitOne ();
507 AsyncResult async = (AsyncResult) asyncResult;
508 GetResponseCallback cb = (GetResponseCallback) async.AsyncDelegate;
509 WebResponse webResponse = cb.EndInvoke(asyncResult);
510 asyncResponding = false;
511 if (webResponse != null) {
512 HttpStatusCode code = ((HttpWebResponse) webResponse).StatusCode;
513 if (code >= HttpStatusCode.MultipleChoices) {
514 string msg = String.Format ("The remote server returned an error: " +
515 "({0}) {1}", (int) code, code);
516 throw new WebException (msg);
523 public override WebResponse GetResponse()
525 IAsyncResult asyncResult = BeginGetResponse (null, null);
526 if (!(asyncResult.AsyncWaitHandle.WaitOne (timeout, false))) {
527 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
529 return EndGetResponse (asyncResult);
532 WebResponse GetResponseInternal ()
534 if (webResponse != null)
537 GetRequestStreamInternal (); // Make sure we do the request
539 webResponse = new HttpWebResponse (this.actualUri,
543 new EventHandler (OnClose));
548 public override void Abort()
550 this.haveResponse = true;
551 throw new NotImplementedException ();
555 void ISerializable.GetObjectData (SerializationInfo serializationInfo,
556 StreamingContext streamingContext)
558 throw new NotImplementedException ();
566 void IDisposable.Dispose ()
569 GC.SuppressFinalize (this);
572 protected virtual void Dispose (bool disposing)
582 requestStream = null;
589 private void CheckRequestStarted ()
592 throw new InvalidOperationException ("request started");
595 internal void Close ()
597 if (requestStream != null)
598 requestStream.Close ();
600 if (webResponse != null)
601 webResponse.Close ();
605 if (requestEndEvent != null)
606 requestEndEvent.Set ();
607 // requestEndEvent = null;
611 void OnClose (object sender, EventArgs args)
613 if (sender == requestStream)
614 requestClosed = true;
615 else if (sender == webResponse)
616 responseClosed = true;
618 // TODO: if both have been used, wait until both are Closed. That's what MS does.
619 if (requestClosed && responseClosed && socket != null && socket.Connected)
628 IPAddress hostAddr = Dns.Resolve (actualUri.Host).AddressList[0];
629 IPEndPoint endPoint = new IPEndPoint (hostAddr, actualUri.Port);
630 socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
632 socket.Connect (endPoint);
633 if (!socket.Poll (timeout, SelectMode.SelectWrite))
634 throw new WebException("Connection timed out.", WebExceptionStatus.Timeout);
641 // to catch the Close called on the NetworkStream
642 internal class HttpWebRequestStream : NetworkStream
645 EventHandler onClose;
647 internal HttpWebRequestStream (HttpWebRequest webRequest,
649 EventHandler onClose)
650 : base (socket, FileAccess.Write, false)
652 this.onClose = onClose;
653 StringBuilder sb = new StringBuilder ();
654 sb.Append (webRequest.Method + " " +
655 webRequest.actualUri.PathAndQuery +
657 webRequest.version.ToString (2) +
660 foreach (string header in webRequest.webHeaders) {
661 sb.Append (header + ": " + webRequest.webHeaders[header] + "\r\n");
664 // FIXME: write cookie headers (CookieContainer not yet implemented)
667 byte [] bytes = Encoding.ASCII.GetBytes (sb.ToString ());
668 if (!socket.Poll (webRequest.Timeout, SelectMode.SelectWrite))
669 throw new WebException("The request timed out", WebExceptionStatus.Timeout);
671 this.Write (bytes, 0, bytes.Length);
674 protected override void Dispose (bool disposing)
681 /* This does not work !??
682 if (socket.Connected)
683 socket.Shutdown (SocketShutdown.Send);
686 onClose (this, EventArgs.Empty);
690 base.Dispose (disposing);
693 public override void Close()
695 GC.SuppressFinalize (this);