1 // 2 // HttpServerTransportSink.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 HttpServerTransportSink : IServerChannelSink 38 { 39 40 IServerChannelSink nextSink; 41 HttpServerTransportSink(IServerChannelSink nextSink)42 public HttpServerTransportSink (IServerChannelSink nextSink) 43 { 44 this.nextSink = nextSink; 45 } 46 47 public IServerChannelSink NextChannelSink 48 { 49 get { return nextSink; } 50 } 51 AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, object state, IMessage msg, ITransportHeaders headers, Stream responseStream)52 public void AsyncProcessResponse (IServerResponseChannelSinkStack sinkStack, object state, 53 IMessage msg, ITransportHeaders headers, Stream responseStream) 54 { 55 ContextWithId ctx = (ContextWithId) state; 56 WriteOut (ctx.Context, headers, responseStream); 57 ctx.Context.Response.Close (); 58 } 59 GetResponseStream(IServerResponseChannelSinkStack sinkStack, object state, IMessage msg, ITransportHeaders headers)60 public Stream GetResponseStream (IServerResponseChannelSinkStack sinkStack, object state, 61 IMessage msg, ITransportHeaders headers) 62 { 63 return null; 64 } 65 ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream)66 public ServerProcessing ProcessMessage (IServerChannelSinkStack sinkStack, IMessage requestMsg, 67 ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, 68 out ITransportHeaders responseHeaders, out Stream responseStream) 69 { 70 //we know we never call this 71 throw new NotSupportedException (); 72 } 73 74 public IDictionary Properties 75 { 76 get 77 { 78 if (nextSink != null) 79 return nextSink.Properties; 80 else return null; 81 } 82 } 83 HandleRequest(HttpListenerContext context)84 internal void HandleRequest (HttpListenerContext context) 85 { 86 //build the headers 87 ITransportHeaders requestHeaders = new TransportHeaders (); 88 System.Collections.Specialized.NameValueCollection httpHeaders = context.Request.Headers; 89 foreach (string key in httpHeaders.Keys) { 90 requestHeaders[key] = httpHeaders[key]; 91 } 92 93 //get an ID for this connection 94 ContextWithId identitiedContext = new ContextWithId (context); 95 96 requestHeaders[CommonTransportKeys.RequestUri] = context.Request.Url.PathAndQuery; 97 requestHeaders[CommonTransportKeys.IPAddress] = context.Request.RemoteEndPoint.Address; 98 requestHeaders[CommonTransportKeys.ConnectionId] = identitiedContext.ID; 99 requestHeaders["__RequestVerb"] = context.Request.HttpMethod; 100 requestHeaders["__HttpVersion"] = string.Format ("HTTP/{0}.{1}", 101 context.Request.ProtocolVersion.Major, context.Request.ProtocolVersion.Minor); 102 103 if (RemotingConfiguration.CustomErrorsEnabled (context.Request.IsLocal)) 104 requestHeaders["__CustomErrorsEnabled"] = false; 105 106 IMessage responseMsg; 107 Stream responseStream; 108 ITransportHeaders responseHeaders; 109 110 // attach the context as state so that our async handler can use it to send the response 111 ServerChannelSinkStack sinkStack = new ServerChannelSinkStack (); 112 sinkStack.Push (this, identitiedContext); 113 114 // NOTE: if we copy the InputStream before passing it so the sinks, the .NET formatters have 115 // unspecified internal errors. Let's hope they don't need to seek the stream! 116 ServerProcessing proc = nextSink.ProcessMessage (sinkStack, null, requestHeaders, context.Request.InputStream, 117 out responseMsg, out responseHeaders, out responseStream); 118 119 switch (proc) { 120 case ServerProcessing.Complete: 121 WriteOut (context, responseHeaders, responseStream); 122 context.Response.Close (); 123 break; 124 125 case ServerProcessing.Async: 126 break; 127 128 case ServerProcessing.OneWay: 129 context.Response.Close (); 130 break; 131 } 132 133 } 134 SynchronousDispatch(ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream)135 internal ServerProcessing SynchronousDispatch (ITransportHeaders requestHeaders, Stream requestStream, 136 out ITransportHeaders responseHeaders, out Stream responseStream) 137 { 138 IMessage responseMsg; 139 ContextWithId identitiedContext = new ContextWithId (null); 140 141 // attach the context as state so that our async handler can use it to send the response 142 ServerChannelSinkStack sinkStack = new ServerChannelSinkStack (); 143 sinkStack.Push (this, identitiedContext); 144 145 return nextSink.ProcessMessage (sinkStack, null, requestHeaders, requestStream, 146 out responseMsg, out responseHeaders, out responseStream); 147 } 148 WriteOut(HttpListenerContext context, ITransportHeaders responseHeaders, Stream responseStream)149 static void WriteOut (HttpListenerContext context, ITransportHeaders responseHeaders, Stream responseStream) 150 { 151 //header processing taken/modified from HttpRemotingHandler 152 if (responseHeaders != null && responseHeaders["__HttpStatusCode"] != null) { 153 context.Response.StatusCode = Convert.ToInt32 (responseHeaders["__HttpStatusCode"]); 154 context.Response.StatusDescription = (string) responseHeaders["__HttpReasonPhrase"]; 155 } 156 157 if (responseHeaders != null) { 158 foreach (DictionaryEntry entry in responseHeaders) { 159 string key = entry.Key.ToString (); 160 if (key != "__HttpStatusCode" && key != "__HttpReasonPhrase") { 161 context.Response.AddHeader ((string)entry.Key, responseHeaders[entry.Key].ToString ()); 162 } 163 } 164 } 165 166 //we need a stream with a length, so if it's not a MemoryStream we copy it 167 MemoryStream ms; 168 if (responseStream is MemoryStream) { 169 ms = (MemoryStream) responseStream; 170 //this seems to be necessary for some third-party formatters 171 //even though my testing suggested .NET doesn't seem to seek incoming streams 172 ms.Position = 0; 173 } else { 174 ms = new MemoryStream (); 175 HttpClientTransportSink.CopyStream (responseStream, ms, 1024); 176 ms.Position = 0; 177 responseStream.Close (); 178 } 179 180 //FIXME: WHY DOES CHUNKING BREAK THE TESTS? 181 //for now, we set the content length so that the server doesn't use chunking 182 context.Response.ContentLength64 = ms.Length; 183 HttpClientTransportSink.CopyStream (ms, context.Response.OutputStream, 1024); 184 ms.Close (); 185 } 186 187 class ContextWithId 188 { 189 static object lockObj = new object (); 190 static int nextId = 0; 191 192 HttpListenerContext context; 193 int id; 194 195 public HttpListenerContext Context { get { return context; } } 196 public int ID { get { return id; } } 197 ContextWithId(HttpListenerContext context)198 public ContextWithId (HttpListenerContext context) 199 { 200 this.context = context; 201 lock (lockObj) { 202 //FIXME: is it really valid to roll arund and reset the ID? 203 unchecked { 204 this.id = nextId++; 205 } 206 } 207 } 208 209 } 210 211 } 212 } 213