2 // HttpRequestChannel.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2006 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System
.Collections
.Generic
;
32 using System
.Net
.Security
;
33 using System
.ServiceModel
;
34 using System
.ServiceModel
.Description
;
35 using System
.ServiceModel
.Security
;
36 using System
.Threading
;
38 namespace System
.ServiceModel
.Channels
40 internal class HttpRequestChannel
: RequestChannelBase
42 HttpChannelFactory
<IRequestChannel
> source
;
44 WebRequest web_request
;
46 // FIXME: supply maxSizeOfHeaders.
47 int max_headers
= 0x10000;
51 public HttpRequestChannel (HttpChannelFactory
<IRequestChannel
> factory
,
52 EndpointAddress address
, Uri via
)
53 : base (factory
, address
, via
)
55 this.source
= factory
;
58 public int MaxSizeOfHeaders
{
59 get { return max_headers; }
62 public MessageEncoder Encoder
{
63 get { return source.MessageEncoder; }
68 public override Message
Request (Message message
, TimeSpan timeout
)
70 return EndRequest (BeginRequest (message
, timeout
, null, null));
73 void BeginProcessRequest (HttpChannelRequestAsyncResult result
)
75 Message message
= result
.Message
;
76 TimeSpan timeout
= result
.Timeout
;
77 // FIXME: is distination really like this?
78 Uri destination
= message
.Headers
.To
;
79 if (destination
== null) {
80 if (source
.Transport
.ManualAddressing
)
81 throw new InvalidOperationException ("When manual addressing is enabled on the transport, every request messages must be set its destination address.");
83 destination
= Via
?? RemoteAddress
.Uri
;
86 web_request
= HttpWebRequest
.Create (destination
);
87 web_request
.Method
= "POST";
88 web_request
.ContentType
= Encoder
.ContentType
;
91 var cmgr
= source
.GetProperty
<IHttpCookieContainerManager
> ();
93 ((HttpWebRequest
) web_request
).CookieContainer
= cmgr
.CookieContainer
;
96 #if !MOONLIGHT // until we support NetworkCredential like SL4 will do.
97 // client authentication (while SL3 has NetworkCredential class, it is not implemented yet. So, it is non-SL only.)
98 var httpbe
= (HttpTransportBindingElement
) source
.Transport
;
99 string authType
= null;
100 switch (httpbe
.AuthenticationScheme
) {
101 // AuthenticationSchemes.Anonymous is the default, ignored.
102 case AuthenticationSchemes
.Basic
:
105 case AuthenticationSchemes
.Digest
:
108 case AuthenticationSchemes
.Ntlm
:
111 case AuthenticationSchemes
.Negotiate
:
112 authType
= "Negotiate";
115 if (authType
!= null) {
116 var cred
= source
.ClientCredentials
;
117 string user
= cred
!= null ? cred
.UserName
.UserName
: null;
118 string pwd
= cred
!= null ? cred
.UserName
.Password
: null;
119 if (String
.IsNullOrEmpty (user
))
120 throw new InvalidOperationException (String
.Format ("Use ClientCredentials to specify a user name for required HTTP {0} authentication.", authType
));
121 var nc
= new NetworkCredential (user
, pwd
);
122 web_request
.Credentials
= nc
;
123 // FIXME: it is said required in SL4, but it blocks full WCF.
124 //web_request.UseDefaultCredentials = false;
128 #if !NET_2_1 // FIXME: implement this to not depend on Timeout property
129 web_request
.Timeout
= (int) timeout
.TotalMilliseconds
;
132 // There is no SOAP Action/To header when AddressingVersion is None.
133 if (message
.Version
.Envelope
.Equals (EnvelopeVersion
.Soap11
) ||
134 message
.Version
.Addressing
.Equals (AddressingVersion
.None
)) {
135 if (message
.Headers
.Action
!= null) {
136 web_request
.Headers
["SOAPAction"] = String
.Concat ("\"", message
.Headers
.Action
, "\"");
137 message
.Headers
.RemoveAll ("Action", message
.Version
.Addressing
.Namespace
);
141 // apply HttpRequestMessageProperty if exists.
142 bool suppressEntityBody
= false;
144 string pname
= HttpRequestMessageProperty
.Name
;
145 if (message
.Properties
.ContainsKey (pname
)) {
146 HttpRequestMessageProperty hp
= (HttpRequestMessageProperty
) message
.Properties
[pname
];
147 web_request
.Headers
.Clear ();
148 web_request
.Headers
.Add (hp
.Headers
);
149 web_request
.Method
= hp
.Method
;
150 // FIXME: do we have to handle hp.QueryString ?
151 if (hp
.SuppressEntityBody
)
152 suppressEntityBody
= true;
156 if (!suppressEntityBody
&& String
.Compare (web_request
.Method
, "GET", StringComparison
.OrdinalIgnoreCase
) != 0) {
157 MemoryStream buffer
= new MemoryStream ();
158 Encoder
.WriteMessage (message
, buffer
);
160 if (buffer
.Length
> int.MaxValue
)
161 throw new InvalidOperationException ("The argument message is too large.");
164 web_request
.ContentLength
= (int) buffer
.Length
;
167 web_request
.BeginGetRequestStream (delegate (IAsyncResult r
) {
169 result
.CompletedSynchronously
&= r
.CompletedSynchronously
;
170 using (Stream s
= web_request
.EndGetRequestStream (r
))
171 s
.Write (buffer
.GetBuffer (), 0, (int) buffer
.Length
);
172 web_request
.BeginGetResponse (GotResponse
, result
);
173 } catch (Exception ex
) {
174 result
.Complete (ex
);
178 web_request
.BeginGetResponse (GotResponse
, result
);
182 void GotResponse (IAsyncResult result
)
184 HttpChannelRequestAsyncResult channelResult
= (HttpChannelRequestAsyncResult
) result
.AsyncState
;
185 channelResult
.CompletedSynchronously
&= result
.CompletedSynchronously
;
190 res
= web_request
.EndGetResponse (result
);
191 resstr
= res
.GetResponseStream ();
192 } catch (WebException we
) {
195 channelResult
.Complete (we
);
199 // The response might contain SOAP fault. It might not.
200 resstr
= res
.GetResponseStream ();
201 } catch (WebException we2
) {
202 channelResult
.Complete (we2
);
207 var hrr
= (HttpWebResponse
) res
;
208 if ((int) hrr
.StatusCode
>= 400) {
209 channelResult
.Complete (new WebException (String
.Format ("There was an error on processing web request: Status code {0}({1}): {2}", (int) hrr
.StatusCode
, hrr
.StatusCode
, hrr
.StatusDescription
)));
213 using (var responseStream
= resstr
) {
214 MemoryStream ms
= new MemoryStream ();
215 byte [] b
= new byte [65536];
219 n
= responseStream
.Read (b
, 0, 65536);
224 ms
.Seek (0, SeekOrigin
.Begin
);
226 channelResult
.Response
= Encoder
.ReadMessage (
227 //responseStream, MaxSizeOfHeaders);
228 ms
, MaxSizeOfHeaders
, res
.ContentType
);
230 MessageBuffer buf = ret.CreateBufferedCopy (0x10000);
231 ret = buf.CreateMessage ();
232 System.Xml.XmlTextWriter w = new System.Xml.XmlTextWriter (Console.Out);
233 w.Formatting = System.Xml.Formatting.Indented;
234 buf.CreateMessage ().WriteMessage (w);
237 channelResult
.Complete ();
239 } catch (Exception ex
) {
240 channelResult
.Complete (ex
);
246 public override IAsyncResult
BeginRequest (Message message
, TimeSpan timeout
, AsyncCallback callback
, object state
)
248 ThrowIfDisposedOrNotOpen ();
250 HttpChannelRequestAsyncResult result
= new HttpChannelRequestAsyncResult (message
, timeout
, callback
, state
);
251 BeginProcessRequest (result
);
255 public override Message
EndRequest (IAsyncResult result
)
258 throw new ArgumentNullException ("result");
259 HttpChannelRequestAsyncResult r
= result
as HttpChannelRequestAsyncResult
;
261 throw new InvalidOperationException ("Wrong IAsyncResult");
268 protected override void OnAbort ()
270 if (web_request
!= null)
271 web_request
.Abort ();
277 protected override void OnClose (TimeSpan timeout
)
279 if (web_request
!= null)
280 web_request
.Abort ();
284 protected override IAsyncResult
OnBeginClose (TimeSpan timeout
, AsyncCallback callback
, object state
)
286 throw new NotImplementedException ();
289 protected override void OnEndClose (IAsyncResult result
)
291 throw new NotImplementedException ();
296 protected override void OnOpen (TimeSpan timeout
)
300 protected override IAsyncResult
OnBeginOpen (TimeSpan timeout
, AsyncCallback callback
, object state
)
302 throw new NotImplementedException ();
305 protected override void OnEndOpen (IAsyncResult result
)
307 throw new NotImplementedException ();
310 class HttpChannelRequestAsyncResult
: IAsyncResult
312 public Message Message
{
316 public TimeSpan Timeout
{
320 AsyncCallback callback
;
321 ManualResetEvent wait
;
324 public HttpChannelRequestAsyncResult (Message message
, TimeSpan timeout
, AsyncCallback callback
, object state
)
326 CompletedSynchronously
= true;
329 this.callback
= callback
;
332 wait
= new ManualResetEvent (false);
335 public Message Response
{
339 public WaitHandle AsyncWaitHandle
{
343 public object AsyncState
{
347 public void Complete ()
352 public void Complete (Exception ex
)
357 // If we've already stored an error, don't replace it
362 if (callback
!= null)
366 public bool CompletedSynchronously
{
370 public bool IsCompleted
{
374 public void WaitEnd ()
377 // FIXME: Do we need to use the timeout? If so, what happens when the timeout is reached.
378 // Is the current request cancelled and an exception thrown? If so we need to pass the
379 // exception to the Complete () method and allow the result to complete 'normally'.
381 // neither Moonlight nor MonoTouch supports contexts (WaitOne default to false)
382 bool result
= wait
.WaitOne (Timeout
);
384 bool result
= wait
.WaitOne (Timeout
, true);
387 throw new TimeoutException ();