目次
- 目的
- System.Net.HttpListener とは
- アプリのコード
- 動作環境の設定手順
- HTTP.sys にサーバURLを登録
- ファイアーウォールに HTTP.sys を許可
- HTTP.sys にサーバ証明書とURLの関連を登録
- 動作確認
目的
- C#でHTTPSサーバアプリを作成し、動作環境の設定を行う。
- 利用するライブラリは System.Net.HttpListener とする。
- System.Net.HttpListener を利用するため、コードよりはむしろ、動作環境の設定方法を残しておきたい。
System.Net.HttpListener とは
概要(MS-DOC)によると、HTTP.sys というアプリ(カーネルモードドライバ)を利用し、HTTPパケットの送受信を行うクラス。
HTTP.sys がSSL通信、HTTP接続の管理、HTTPヘッダの解析を行ってくれる。
API(MS-DOC)より、SSL証明書の追加には netsh コマンドを用いる必要がある。
WebSocket に関しては、Windows 8 以降では対応している。
アプリのコード
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var server = new SampleServer();
server.Start(
"http://+:8080/sample/",
"https://+:44300/sample/");
char ch;
while ((ch = Console.ReadKey().KeyChar) != 'q')
;
server.Stop();
}
}
class SampleServer
{
HttpListener listener;
public void Start(params string[] prefixes)
{
listener = new HttpListener();
foreach (var prefix in prefixes)
listener.Prefixes.Add(prefix);
listener.Start();
listener.BeginGetContext(OnRequested, null);
}
void OnRequested(IAsyncResult ar)
{
if (!listener.IsListening)
return;
HttpListenerContext context = listener.EndGetContext(ar);
listener.BeginGetContext(OnRequested, listener);
try
{
if (ProcessGetRequest(context))
return;
if (ProcessPostRequest(context))
return;
if (ProcessWebSocketRequest(context))
return;
}
catch (Exception e)
{
ReturnInternalError(context.Response, e);
}
}
static bool CanAccept(HttpMethod expected, string requested)
{
return string.Equals(expected.Method, requested, StringComparison.CurrentCultureIgnoreCase);
}
static bool ProcessGetRequest(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
if (!CanAccept(HttpMethod.Get, request.HttpMethod) || request.IsWebSocketRequest)
return false;
response.StatusCode = (int)HttpStatusCode.OK;
using (var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
writer.WriteLine($"you have sent headers:\n{request.Headers}");
response.Close();
return true;
}
static bool ProcessPostRequest(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
if (!CanAccept(HttpMethod.Post, request.HttpMethod))
return false;
response.StatusCode = (int)HttpStatusCode.OK;
using (var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
writer.WriteLine($"you have sent headers:\n{request.Headers}");
response.Close();
return true;
}
static bool ProcessWebSocketRequest(HttpListenerContext context)
{
if (!context.Request.IsWebSocketRequest)
return false;
WebSocket webSocket = context.AcceptWebSocketAsync(null).Result.WebSocket;
ProcessReceivedMessage(webSocket, message =>
{
webSocket.SendAsync(
Encoding.UTF8.GetBytes($"you have sent message:\n{message}"),
WebSocketMessageType.Text,
true,
CancellationToken.None);
});
return true;
}
static async void ProcessReceivedMessage(WebSocket webSocket, Action<string> onMessage)
{
var buffer = new ArraySegment<byte>(new byte[1024]);
while (webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult receiveResult = await webSocket.ReceiveAsync(
buffer,
CancellationToken.None);
if (receiveResult.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(
buffer
.Slice(0, receiveResult.Count)
.ToArray());
onMessage.Invoke(message);
}
}
}
static void ReturnInternalError(HttpListenerResponse response, Exception cause)
{
Console.Error.WriteLine(cause);
response.StatusCode = (int)HttpStatusCode.InternalServerError;
response.ContentType = "text/plain";
try
{
using (var writer = new StreamWriter(response.OutputStream, Encoding.UTF8))
writer.Write(cause.ToString());
response.Close();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
response.Abort();
}
}
public void Stop()
{
listener.Stop();
listener.Close();
}
}
}
動作環境の設定手順
1.HTTP.sys にサーバURLを登録
サーバアプリを管理者権限で実行するなら必要ないが、一般ユーザで実行する場合はあらかじめHTTP.sysが監視するURLを登録しておく必要がある。
コマンドプロンプトから netsh コマンドを実行することになるが、コマンドプロンプトの起動は管理者として行うこと。
登録するURLは HttpListener.Prefixes.Add() に渡した値でよい。
> netsh http add urlacl url=http://+:8080/sample/ user=Everyone
> netsh http add urlacl url=https://+:44300/sample/ user=Everyone
2.ファイアーウォールに HTTP.sys を許可
コントロールパネルの「Windows ファイアーウォール」>「詳細設定」>「受信の規則」に「新しい規則」を追加する。
新しい規則は system プログラムを許可するような規則とする。
3.HTTP.sys にサーバ証明書とURLの関連を登録
管理者として起動したコマンドプロンプトから netsh コマンドを実行する。
サーバ証明書はあらかじめ登録しておく。pfx ファイルをエクスプローラから実行すればよい。その際、「保存場所」は「ローカルコンピュータ」にしておくこと。
その後、サーバ証明書の拇印を確認するため、mmc を起動し、スナップインに証明書を追加し、「証明書(ローカルコンピュータ)」>「個人」>「証明書」に表示される証明書を選択し、拇印を確認する。
URLは ワイルドカードが使えないので、具体的に指定する。
GUID も必要になるが、なんでもよい。
> netsh http add sslcert ipport=localhost:44300 certhash=<サーバ証明書の拇印> appid={00112233-4455-6677-8899-AABBCCDDEEFF}
> netsh http add sslcert ipport=127.0.0.1:44300 certhash=<サーバ証明書の拇印> appid={00112233-4455-6677-8899-AABBCCDDEEFF}
動作確認
-
Get の確認方法
ブラウザで https://127.0.0.1:44300 にアクセスし、応答を確認する -
WebSocket の確認方法
ブラウザで https://www.websocket.org/echo.html にアクセスし、「Location」に 「wss://127.0.0.1:44300」を入力後、「Connect」を押下し、「Log」を確認する