2 // HttpClientTransportSink.cs
5 // Michael Hutchinson <mhutchinson@novell.com>
7 // Copyright (C) 2008 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.
30 using System
.Collections
;
33 using System
.Runtime
.Remoting
.Messaging
;
35 namespace System
.Runtime
.Remoting
.Channels
.Http
37 class HttpClientTransportSink
: IClientChannelSink
40 HttpClientChannel channel
;
42 public HttpClientTransportSink (HttpClientChannel channel
, string url
)
44 this.channel
= channel
;
48 //always the last sink in the chain
49 public IClientChannelSink NextChannelSink
54 public void AsyncProcessRequest (IClientChannelSinkStack sinkStack
, IMessage msg
,
55 ITransportHeaders headers
, Stream requestStream
)
57 bool isOneWay
= RemotingServices
.IsOneWay (((IMethodMessage
)msg
).MethodBase
);
59 HttpWebRequest request
= CreateRequest (headers
);
61 Stream targetStream
= request
.GetRequestStream ();
62 CopyStream (requestStream
, targetStream
, 1024);
63 targetStream
.Close ();
66 sinkStack
.Push (this, request
);
67 request
.BeginGetResponse (new AsyncCallback (AsyncProcessResponseCallback
), sinkStack
);
71 void AsyncProcessResponseCallback (IAsyncResult ar
)
73 IClientChannelSinkStack sinkStack
= (IClientChannelSinkStack
)ar
.AsyncState
;
74 HttpWebRequest request
= (HttpWebRequest
)sinkStack
.Pop(this);
78 response
= request
.EndGetResponse (ar
);
79 } catch (WebException ex
) {
80 response
= ex
.Response
;
81 //only error 500 is handled by the remoting stack
82 HttpWebResponse httpResponse
= response
as HttpWebResponse
;
83 if (httpResponse
== null || httpResponse
.StatusCode
!= HttpStatusCode
.InternalServerError
)
87 //this is only valid after the response is fetched
88 SetConnectionLimit (request
);
90 Stream responseStream
= response
.GetResponseStream ();
91 ITransportHeaders responseHeaders
= GetHeaders (response
);
92 sinkStack
.AsyncProcessResponse (responseHeaders
, responseStream
);
95 public void AsyncProcessResponse (IClientResponseChannelSinkStack sinkStack
, object state
,
96 ITransportHeaders headers
, Stream stream
)
98 // Should never be called
99 throw new NotSupportedException ();
102 public Stream
GetRequestStream (IMessage msg
, ITransportHeaders headers
)
107 HttpWebRequest
CreateRequest (ITransportHeaders requestHeaders
)
109 string url
= this.url
;
112 //FIXME: requestUri should contain the URL-less URI only when it's a CAO call;
113 // at all other times it should be null. On Mono, whenever it should be null, it contains the full
114 // URL+URI, so we have a broken mixure of path types and we need to hack around it
115 string requestUri
= requestHeaders
[CommonTransportKeys
.RequestUri
] as string;
117 if (requestUri
!= null && HttpChannel
.ParseInternal (requestUri
, out objectURI
) == null) {
118 url
= HttpChannel
.ParseInternal (url
, out objectURI
);
119 if (!url
.EndsWith ("/"))
121 url
= url
+ requestUri
;
124 HttpWebRequest request
= (HttpWebRequest
)WebRequest
.Create (url
);
125 request
.UserAgent
= string.Format ("Mozilla/4.0+(compatible; Mono Remoting; Mono {0})",
126 System
.Environment
.Version
);
128 //Only set these if they deviate from the defaults, as some map to
129 //properties that throw NotImplementedExceptions
130 if (channel
.Timeout
!= -1)
131 request
.Timeout
= channel
.Timeout
;
132 if (channel
.AllowAutoRedirect
== false)
133 request
.AllowAutoRedirect
= false;
134 if (channel
.Credentials
!= null)
135 request
.Credentials
= channel
.Credentials
;
137 else if (channel
.UseDefaultCredentials
== true)
138 request
.UseDefaultCredentials
= true;
140 else if (channel
.Username
!= null && channel
.Username
.Length
> 0) {
141 if (channel
.Domain
!= null && channel
.Domain
.Length
> 0) {
142 request
.Credentials
= new NetworkCredential (channel
.Username
, channel
.Password
,
145 request
.Credentials
= new NetworkCredential (channel
.Username
, channel
.Password
);
149 if (channel
.UnsafeAuthenticatedConnectionSharing
== true)
150 request
.UnsafeAuthenticatedConnectionSharing
= true;
151 if (channel
.ConnectionGroupName
!= null)
152 request
.ConnectionGroupName
= channel
.ConnectionGroupName
;
155 FIXME: implement these
161 UseAuthenticatedConnectionSharing
165 request
.ContentType
= (string)requestHeaders
["Content-Type"];
167 //BUG: Mono formatters/dispatcher don't set this. Something in the MS stack does.
168 string method
= (string)requestHeaders
["__RequestVerb"];
171 request
.Method
= method
;
173 foreach (DictionaryEntry entry
in requestHeaders
) {
174 string key
= entry
.Key
.ToString ();
175 if (key
!= "__RequestVerb" && key
!= "Content-Type" && key
!= CommonTransportKeys
.RequestUri
) {
176 request
.Headers
.Add (key
, entry
.Value
.ToString ());
182 void SetConnectionLimit (HttpWebRequest request
)
184 if (channel
.ClientConnectionLimit
!= 2) {
185 request
.ServicePoint
.ConnectionLimit
= channel
.ClientConnectionLimit
;
189 static TransportHeaders
GetHeaders (WebResponse response
)
191 TransportHeaders headers
= new TransportHeaders ();
192 foreach (string key
in response
.Headers
) {
193 headers
[key
] = response
.Headers
[key
];
198 internal static void CopyStream (Stream source
, Stream target
, int bufferSize
)
200 byte[] buffer
= new byte[bufferSize
];
201 int readLen
= source
.Read (buffer
, 0, buffer
.Length
);
202 while (readLen
> 0) {
203 target
.Write (buffer
, 0, readLen
);
204 readLen
= source
.Read (buffer
, 0, buffer
.Length
);
208 public void ProcessMessage (IMessage msg
, ITransportHeaders requestHeaders
, Stream requestStream
,
209 out ITransportHeaders responseHeaders
, out Stream responseStream
)
211 HttpWebRequest request
= CreateRequest (requestHeaders
);
212 Stream targetStream
= request
.GetRequestStream ();
213 CopyStream (requestStream
, targetStream
, 1024);
214 targetStream
.Close ();
216 WebResponse response
;
218 response
= request
.GetResponse ();
219 } catch (WebException ex
) {
220 response
= ex
.Response
;
221 //only error 500 is handled by the remoting stack
222 HttpWebResponse httpResponse
= response
as HttpWebResponse
;
223 if (httpResponse
== null || httpResponse
.StatusCode
!= HttpStatusCode
.InternalServerError
)
227 //this is only valid after the response is fetched
228 SetConnectionLimit (request
);
230 //FIXME: can we assume that the formatters will close the stream? Or do we need to make
231 // a copy and close it ourselves?
232 responseHeaders
= GetHeaders (response
);
233 responseStream
= response
.GetResponseStream ();
236 public IDictionary Properties