1 //
2 // Mono.WebServer.BaseRequestBroker
3 //
4 // Authors:
5 //	Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // 	Lluis Sanchez Gual (lluis@ximian.com)
7 //
8 // (C) Copyright 2004-2010 Novell, Inc
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29 
30 
31 using System;
32 
33 namespace Mono.WebServer
34 {
35 	public class BaseRequestBroker: MarshalByRefObject, IRequestBroker
36 	{
37 		public event UnregisterRequestEventHandler UnregisterRequestEvent;
38 
39 		//  Contains the initial request capacity of a BaseRequestBroker
40 		const int INITIAL_REQUESTS = 200;
41 
42 		//  The size of a request buffer in bytes.
43 		//
44 		//  This number should be equal to INPUT_BUFFER_SIZE
45 		//  in System.Web.HttpRequest.
46 		const int BUFFER_SIZE = 32*1024;
47 
48 		// Contains a lock to use when accessing and modifying the
49 		// request allocation tables.
50 		static readonly object reqlock = new object();
51 
52 		// Contains the request ID's.
53 		int[] request_ids = new int [INITIAL_REQUESTS];
54 
55 		// Contains the registered workers.
56 		Worker[] requests = new Worker [INITIAL_REQUESTS];
57 
58 		// Contains buffers for the requests to use.
59 		byte[][] buffers = new byte [INITIAL_REQUESTS][];
60 
61 		// Contains the number of active requests.
62 		int requests_count;
63 
64 		// Contains the total number of requests served so far.
65 		// May freely wrap around.
66 		uint requests_served;
67 
68 		/// <summary>
69 		/// Grows the size of the request allocation tables by 33%.
70 		///
71 		/// This *MUST* be called with the reqlock held!
72 		/// </summary>
73 		/// <returns>ID to use for a new request.</returns>
74 		/// <param name="curlen">Current length of the allocation tables.</param>
GrowRequests(ref int curlen)75 		int GrowRequests (ref int curlen)
76 		{
77 			int newsize = curlen + curlen/3;
78 			var new_request_ids = new int [newsize];
79 			var new_requests = new Worker [newsize];
80 			var new_buffers = new byte [newsize][];
81 
82 			request_ids.CopyTo (new_request_ids, 0);
83 			Array.Clear (request_ids, 0, request_ids.Length);
84 			request_ids = new_request_ids;
85 
86 			requests.CopyTo (new_requests, 0);
87 			Array.Clear (requests, 0, requests.Length);
88 			requests = new_requests;
89 
90 			buffers.CopyTo (new_buffers, 0);
91 			Array.Clear (buffers, 0, buffers.Length);
92 			buffers = new_buffers;
93 
94 			curlen = newsize;
95 			return curlen + 1;
96 		}
97 
98 		/// <summary>
99 		/// Gets the next available request ID, expanding the array
100 		/// of possible ID's if necessary.
101 		///
102 		/// This *MUST* be called with the reqlock held!
103 		/// </summary>
104 		/// <returns>ID of the request.</returns>
GetNextRequestId()105 		int GetNextRequestId ()
106 		{
107 			int reqlen = request_ids.Length;
108 
109 			requests_served++; // increment to 1 before putting into request_ids
110 					   // so that the 0 id is reserved for slot not used
111 			if (requests_served == 0x8000) // and check for wrap-around for the above
112 				requests_served = 1; // making sure we don't exceed 0x7FFF or go negative
113 
114 			requests_count++;
115 
116 			int newid;
117 			if (requests_count >= reqlen)
118 				newid = GrowRequests (ref reqlen);
119 			else
120 				newid = Array.IndexOf (request_ids, 0);
121 
122 			if (newid == -1) {
123 				// Should never happen...
124 				throw new ApplicationException ("could not allocate new request id");
125 			}
126 
127 			// TODO: newid had better not exceed 0xFFFF.
128 			newid = ((ushort)newid & 0xFFFF) | (((ushort)requests_served & 0x7FFF) << 16);
129 			request_ids [IdToIndex(newid)] = newid;
130 			return newid;
131 		}
132 
RegisterRequest(Worker worker)133 		public int RegisterRequest (Worker worker)
134 		{
135 			int result;
136 
137 			lock (reqlock) {
138 				result = IdToIndex (GetNextRequestId ());
139 				requests [result] = worker;
140 
141 				// Don't create a new array if one already exists.
142 				byte[] a = buffers [result];
143 				if (a == null)
144 					buffers [result] = new byte [BUFFER_SIZE];
145 			}
146 
147 			return request_ids [result];
148 		}
149 
IdToIndex(int requestId)150 		int IdToIndex(int requestId) {
151 			return requestId & 0xFFFF;
152 		}
153 
UnregisterRequest(int id)154 		public void UnregisterRequest (int id)
155 		{
156 			lock (reqlock) {
157 				if (!ValidRequest (id))
158 					return;
159 
160 				DoUnregisterRequest (id);
161 				int idx = IdToIndex (id);
162 
163 				byte[] a = buffers [idx];
164 				if (a != null)
165 					Array.Clear (a, 0, a.Length);
166 				requests [idx] = null;
167 				request_ids [idx] = 0;
168 				requests_count--;
169 			}
170 		}
171 
172 		/// <summary>
173 		/// Invokes registered handlers of UnregisterRequestEvent. Each handler is passed an
174 		/// arguments object which contains the ID of a request that is about to be
175 		/// unregistered.
176 		/// </summary>
177 		/// <param name="id">ID of a request that is about to be unregistered.</param>
DoUnregisterRequest(int id)178 		void DoUnregisterRequest (int id)
179 		{
180 			if (UnregisterRequestEvent == null)
181 				return;
182 			Delegate[] handlers = UnregisterRequestEvent.GetInvocationList ();
183 			if (handlers == null || handlers.Length == 0)
184 				return;
185 
186 			var args = new UnregisterRequestEventArgs (id);
187 			foreach (UnregisterRequestEventHandler handler in handlers)
188 				handler (this, args);
189 		}
190 
ValidRequest(int requestId)191 		protected bool ValidRequest (int requestId)
192 		{
193 			int idx = IdToIndex (requestId);
194 			return (idx >= 0 && idx < request_ids.Length && request_ids [idx] == requestId &&
195 				buffers [idx] != null);
196 		}
197 
Read(int requestId, int size, out byte[] buffer)198 		public int Read (int requestId, int size, out byte[] buffer)
199 		{
200 			buffer = null;
201 
202 			Worker w;
203 
204 			lock (reqlock) {
205 				if (!ValidRequest (requestId))
206 					return 0;
207 
208 				w = GetWorker (requestId);
209 				if (w == null)
210 					return 0;
211 
212 				// Use a pre-allocated buffer only when the size matches
213 				// as it will be transferred across appdomain boundaries
214 				// in full length
215 				if (size == BUFFER_SIZE) {
216 					buffer = buffers [IdToIndex (requestId)];
217 				} else {
218 					buffer = new byte[size];
219 				}
220 			}
221 
222 			return w.Read (buffer, 0, size);
223 		}
224 
GetWorker(int requestId)225 		public Worker GetWorker (int requestId)
226 		{
227 			lock (reqlock) {
228 				if (!ValidRequest (requestId))
229 					return null;
230 
231 				return requests [IdToIndex (requestId)];
232 			}
233 		}
234 
Write(int requestId, byte[] buffer, int position, int size)235 		public void Write (int requestId, byte[] buffer, int position, int size)
236 		{
237 			Worker worker = GetWorker (requestId);
238 			if (worker != null)
239 				worker.Write (buffer, position, size);
240 		}
241 
Close(int requestId)242 		public void Close (int requestId)
243 		{
244 			Worker worker = GetWorker (requestId);
245 			if (worker != null)
246 				worker.Close ();
247 		}
248 
Flush(int requestId)249 		public void Flush (int requestId)
250 		{
251 			Worker worker = GetWorker (requestId);
252 			if (worker != null)
253 				worker.Flush ();
254 		}
255 
IsConnected(int requestId)256 		public bool IsConnected (int requestId)
257 		{
258 			Worker worker = GetWorker (requestId);
259 
260 			return (worker != null && worker.IsConnected ());
261 		}
262 
InitializeLifetimeService()263 		public override object InitializeLifetimeService ()
264 		{
265 			return null;
266 		}
267 	}
268 }
269