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 using (Stream targetStream = request.GetRequestStream ()) {
62 CopyStream (requestStream, targetStream, 1024);
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) {
84 sinkStack.DispatchException (ex);
89 //this is only valid after the response is fetched
90 SetConnectionLimit (request);
93 Stream responseStream = response.GetResponseStream ();
94 ITransportHeaders responseHeaders = GetHeaders (response);
95 sinkStack.AsyncProcessResponse (responseHeaders, responseStream);
99 public void AsyncProcessResponse (IClientResponseChannelSinkStack sinkStack, object state,
100 ITransportHeaders headers, Stream stream)
102 // Should never be called
103 throw new NotSupportedException ();
106 public Stream GetRequestStream (IMessage msg, ITransportHeaders headers)
111 HttpWebRequest CreateRequest (ITransportHeaders requestHeaders)
113 string url = this.url;
116 //FIXME: requestUri should contain the URL-less URI only when it's a CAO call;
117 // at all other times it should be null. On Mono, whenever it should be null, it contains the full
118 // URL+URI, so we have a broken mixure of path types and we need to hack around it
119 string requestUri = requestHeaders[CommonTransportKeys.RequestUri] as string;
121 if (requestUri != null && HttpChannel.ParseInternal (requestUri, out objectURI) == null) {
122 url = HttpChannel.ParseInternal (url, out objectURI);
123 if (!url.EndsWith ("/"))
125 url = url + requestUri;
128 HttpWebRequest request = (HttpWebRequest)WebRequest.Create (url);
129 request.UserAgent = string.Format ("Mozilla/4.0+(compatible; Mono Remoting; Mono {0})",
130 System.Environment.Version);
132 //Only set these if they deviate from the defaults, as some map to
133 //properties that throw NotImplementedExceptions
134 if (channel.Timeout != -1)
135 request.Timeout = channel.Timeout;
136 if (channel.AllowAutoRedirect == false)
137 request.AllowAutoRedirect = false;
138 if (channel.Credentials != null)
139 request.Credentials = channel.Credentials;
140 else if (channel.UseDefaultCredentials == true)
141 request.UseDefaultCredentials = true;
142 else if (channel.Username != null && channel.Username.Length > 0) {
143 if (channel.Domain != null && channel.Domain.Length > 0) {
144 request.Credentials = new NetworkCredential (channel.Username, channel.Password,
147 request.Credentials = new NetworkCredential (channel.Username, channel.Password);
151 if (channel.UnsafeAuthenticatedConnectionSharing == true)
152 request.UnsafeAuthenticatedConnectionSharing = true;
153 if (channel.ConnectionGroupName != null)
154 request.ConnectionGroupName = channel.ConnectionGroupName;
157 FIXME: implement these
163 UseAuthenticatedConnectionSharing
167 request.ContentType = (string)requestHeaders["Content-Type"];
169 //BUG: Mono formatters/dispatcher don't set this. Something in the MS stack does.
170 string method = (string)requestHeaders["__RequestVerb"];
173 request.Method = method;
175 foreach (DictionaryEntry entry in requestHeaders) {
176 string key = entry.Key.ToString ();
177 if (key != "__RequestVerb" && key != "Content-Type" && key != CommonTransportKeys.RequestUri) {
178 request.Headers.Add (key, entry.Value.ToString ());
184 void SetConnectionLimit (HttpWebRequest request)
186 if (channel.ClientConnectionLimit != 2) {
187 request.ServicePoint.ConnectionLimit = channel.ClientConnectionLimit;
191 static TransportHeaders GetHeaders (WebResponse response)
193 TransportHeaders headers = new TransportHeaders ();
194 foreach (string key in response.Headers) {
195 headers[key] = response.Headers[key];
200 internal static void CopyStream (Stream source, Stream target, int bufferSize)
202 byte[] buffer = new byte[bufferSize];
203 int readLen = source.Read (buffer, 0, buffer.Length);
204 while (readLen > 0) {
205 target.Write (buffer, 0, readLen);
206 readLen = source.Read (buffer, 0, buffer.Length);
210 public void ProcessMessage (IMessage msg, ITransportHeaders requestHeaders, Stream requestStream,
211 out ITransportHeaders responseHeaders, out Stream responseStream)
213 HttpWebRequest request = CreateRequest (requestHeaders);
214 Stream targetStream = request.GetRequestStream ();
215 CopyStream (requestStream, targetStream, 1024);
216 targetStream.Close ();
218 WebResponse response;
220 response = request.GetResponse ();
221 } catch (WebException ex) {
222 response = ex.Response;
223 //only error 500 is handled by the remoting stack
224 HttpWebResponse httpResponse = response as HttpWebResponse;
225 if (httpResponse == null || httpResponse.StatusCode != HttpStatusCode.InternalServerError)
229 //this is only valid after the response is fetched
230 SetConnectionLimit (request);
232 //FIXME: can we assume that the formatters will close the stream? Or do we need to make
233 // a copy and close it ourselves?
234 responseHeaders = GetHeaders (response);
235 responseStream = response.GetResponseStream ();
238 public IDictionary Properties