summaryrefslogtreecommitdiffstats
path: root/src/OpenID/OpenIdOfflineProvider/HttpHost.cs
blob: 692307edee1a3420f416d52243b55d5a6d2dc76e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//-----------------------------------------------------------------------
// <copyright file="HttpHost.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.OpenIdOfflineProvider {
	using System;
	using System.Diagnostics.Contracts;
	using System.Globalization;
	using System.IO;
	using System.Net;
	using System.Threading;
	using DotNetOpenAuth.Messaging;
	using DotNetOpenAuth.OpenId.Provider;

	/// <summary>
	/// An HTTP Listener that dispatches incoming requests for handling.
	/// </summary>
	internal class HttpHost : IDisposable {
		/// <summary>
		/// The HttpListener that waits for incoming requests.
		/// </summary>
		private readonly HttpListener listener;

		/// <summary>
		/// The thread that listens for incoming HTTP requests and dispatches them
		/// to the <see cref="handler"/>.
		/// </summary>
		private Thread listenerThread;

		/// <summary>
		/// The handler for incoming HTTP requests.
		/// </summary>
		private RequestHandler handler;

		/// <summary>
		/// Initializes a new instance of the <see cref="HttpHost"/> class.
		/// </summary>
		/// <param name="handler">The handler for incoming HTTP requests.</param>
		private HttpHost(RequestHandler handler) {
			Contract.Requires(handler != null);

			this.Port = 45235;
			this.handler = handler;
			Random r = new Random();
		tryAgain:
			try {
				this.listener = new HttpListener();
				this.listener.Prefixes.Add(string.Format(CultureInfo.InvariantCulture, "http://localhost:{0}/", this.Port));
				this.listener.Start();
			} catch (HttpListenerException ex) {
				if (ex.Message.Contains("conflicts")) {
					this.Port += r.Next(1, 20);
					goto tryAgain;
				}
				throw;
			}

			this.listenerThread = new Thread(this.ProcessRequests);
			this.listenerThread.Start();
		}

		/// <summary>
		/// The request handler delegate.
		/// </summary>
		/// <param name="context">Information on the incoming HTTP request.</param>
		internal delegate void RequestHandler(HttpListenerContext context);

		/// <summary>
		/// Gets the port that HTTP requests are being listened for on.
		/// </summary>
		public int Port { get; private set; }

		/// <summary>
		/// Gets the base URI for all incoming web requests that will be received.
		/// </summary>
		public Uri BaseUri {
			get { return new Uri("http://localhost:" + this.Port.ToString() + "/"); }
		}

		/// <summary>
		/// Creates the HTTP host.
		/// </summary>
		/// <param name="handler">The handler for incoming HTTP requests.</param>
		/// <returns>The instantiated host.</returns>
		public static HttpHost CreateHost(RequestHandler handler) {
			Contract.Requires(handler != null);
			Contract.Ensures(Contract.Result<HttpHost>() != null);

			return new HttpHost(handler);
		}

		#region IDisposable Members

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose() {
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing) {
			if (disposing) {
				this.listener.Close();
				this.listenerThread.Join(1000);
				this.listenerThread.Abort();
			}
		}

		#endregion

		/// <summary>
		/// The HTTP listener thread body.
		/// </summary>
		private void ProcessRequests() {
			Contract.Requires(this.listener != null);

			while (true) {
				try {
					HttpListenerContext context = this.listener.GetContext();
					this.handler(context);
				} catch (HttpListenerException ex) {
					if (this.listener.IsListening) {
						App.Logger.Error("Unexpected exception.", ex);
					} else {
						// the listener is probably being shut down
						App.Logger.Warn("HTTP listener is closing down.", ex);
						break;
					}
				}
			}
		}
	}
}