Merge pull request #1222 from LogosBible/uri-trycreate
[mono.git] / mcs / class / System.Runtime.Remoting / System.Runtime.Remoting.Channels.Http / HttpClientTransportSink.cs
1 //
2 // HttpClientTransportSink.cs
3 // 
4 // Author:
5 //   Michael Hutchinson <mhutchinson@novell.com>
6 // 
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 // 
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28
29 using System;
30 using System.Collections;
31 using System.IO;
32 using System.Net;
33 using System.Runtime.Remoting.Messaging;
34
35 namespace System.Runtime.Remoting.Channels.Http
36 {
37         class HttpClientTransportSink : IClientChannelSink
38         {
39                 string url;
40                 HttpClientChannel channel;
41
42                 public HttpClientTransportSink (HttpClientChannel channel, string url)
43                 {
44                         this.channel = channel;
45                         this.url = url;
46                 }
47
48                 //always the last sink in the chain
49                 public IClientChannelSink NextChannelSink
50                 {
51                         get { return null; }
52                 }
53
54                 public void AsyncProcessRequest (IClientChannelSinkStack sinkStack, IMessage msg,
55                         ITransportHeaders headers, Stream requestStream)
56                 {
57                         bool isOneWay = RemotingServices.IsOneWay (((IMethodMessage)msg).MethodBase);
58                         
59                         HttpWebRequest request = CreateRequest (headers);
60         
61                         using (Stream targetStream = request.GetRequestStream ()) {
62                                 CopyStream (requestStream, targetStream, 1024);
63                         }
64
65                         if (!isOneWay) {
66                                 sinkStack.Push (this, request);
67                                 request.BeginGetResponse (new AsyncCallback (AsyncProcessResponseCallback), sinkStack);
68                         }
69                 }
70                 
71                 void AsyncProcessResponseCallback (IAsyncResult ar)
72                 {
73                         IClientChannelSinkStack sinkStack = (IClientChannelSinkStack)ar.AsyncState;
74                         HttpWebRequest request = (HttpWebRequest)sinkStack.Pop(this);
75                         
76                         WebResponse response;
77                         try {
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);
85                                         return;
86                                 }
87                         }
88                         
89                         //this is only valid after the response is fetched
90                         SetConnectionLimit (request);
91
92                         using (response) {
93                                 Stream responseStream = response.GetResponseStream ();
94                                 ITransportHeaders responseHeaders = GetHeaders (response);
95                                 sinkStack.AsyncProcessResponse (responseHeaders, responseStream);
96                         }
97                 }
98
99                 public void AsyncProcessResponse (IClientResponseChannelSinkStack sinkStack, object state,
100                         ITransportHeaders headers, Stream stream)
101                 {
102                         // Should never be called
103                         throw new NotSupportedException ();
104                 }
105
106                 public Stream GetRequestStream (IMessage msg, ITransportHeaders headers)
107                 {
108                         return null;
109                 }
110
111                 HttpWebRequest CreateRequest (ITransportHeaders requestHeaders)
112                 {
113                         string url = this.url;
114                         
115                         
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;
120                         string objectURI;
121                         if (requestUri != null && HttpChannel.ParseInternal (requestUri, out objectURI) == null) {
122                                 url = HttpChannel.ParseInternal (url, out objectURI);
123                                 if (!url.EndsWith ("/"))
124                                         url = url + "/";
125                                 url = url + requestUri;
126                         }
127                         
128                         HttpWebRequest request = (HttpWebRequest)WebRequest.Create (url);
129                         request.UserAgent = string.Format ("Mozilla/4.0+(compatible; Mono Remoting; Mono {0})",
130                                 System.Environment.Version);
131                         
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,
145                                                 channel.Domain);
146                                 } else {
147                                         request.Credentials = new NetworkCredential (channel.Username, channel.Password);
148                                 }
149                         }
150                         
151                         if (channel.UnsafeAuthenticatedConnectionSharing == true)
152                                 request.UnsafeAuthenticatedConnectionSharing = true;
153                         if (channel.ConnectionGroupName != null)
154                                 request.ConnectionGroupName = channel.ConnectionGroupName;
155                         
156                         /*
157                         FIXME: implement these
158                         MachineName
159                         ProxyName
160                         ProxyPort
161                         ProxyUri
162                         ServicePrincipalName
163                         UseAuthenticatedConnectionSharing
164                         */
165                         
166                         //build the headers
167                         request.ContentType = (string)requestHeaders["Content-Type"];
168                         
169                         //BUG: Mono formatters/dispatcher don't set this. Something in the MS stack does.
170                         string method = (string)requestHeaders["__RequestVerb"];
171                         if (method == null)
172                                 method = "POST";
173                         request.Method = method;
174                         
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 ());
179                                 }
180                         }
181                         return request;
182                 }
183                 
184                 void SetConnectionLimit (HttpWebRequest request)
185                 {
186                         if (channel.ClientConnectionLimit != 2) {
187                                 request.ServicePoint.ConnectionLimit = channel.ClientConnectionLimit;
188                         }
189                 }
190
191                 static TransportHeaders GetHeaders (WebResponse response)
192                 {
193                         TransportHeaders headers = new TransportHeaders ();
194                         foreach (string key in response.Headers) {
195                                 headers[key] = response.Headers[key];
196                         }
197                         return headers;
198                 }
199
200                 internal static void CopyStream (Stream source, Stream target, int bufferSize)
201                 {
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);
207                         }
208                 }
209
210                 public void ProcessMessage (IMessage msg, ITransportHeaders requestHeaders, Stream requestStream,
211                         out ITransportHeaders responseHeaders, out Stream responseStream)
212                 {
213                         HttpWebRequest request = CreateRequest (requestHeaders);
214                         Stream targetStream = request.GetRequestStream ();
215                         CopyStream (requestStream, targetStream, 1024);
216                         targetStream.Close ();
217                         
218                         WebResponse response;
219                         try {
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)
226                                         throw;
227                         }
228                         
229                         //this is only valid after the response is fetched
230                         SetConnectionLimit (request);
231                         
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 ();
236                 }
237
238                 public IDictionary Properties
239                 {
240                         get { return null; }
241                 }
242         }
243 }