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 HttpClientTransportSink(HttpClientChannel channel, string url)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 AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg, ITransportHeaders headers, Stream requestStream)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 AsyncProcessResponseCallback(IAsyncResult ar)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 AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, object state, ITransportHeaders headers, Stream stream)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 GetRequestStream(IMessage msg, ITransportHeaders headers)106 public Stream GetRequestStream (IMessage msg, ITransportHeaders headers) 107 { 108 return null; 109 } 110 CreateRequest(ITransportHeaders requestHeaders)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 SetConnectionLimit(HttpWebRequest request)184 void SetConnectionLimit (HttpWebRequest request) 185 { 186 if (channel.ClientConnectionLimit != 2) { 187 request.ServicePoint.ConnectionLimit = channel.ClientConnectionLimit; 188 } 189 } 190 GetHeaders(WebResponse response)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 CopyStream(Stream source, Stream target, int bufferSize)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 ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream)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 } 244