#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace Nuclex.Networking.Http {
/// Handles a connection from an HTTP client
public class ClientConnection : SocketReceiver {
/// Default size for the incoming data buffer
private const int DefaultBufferSize = 256;
/// Initializes a new connection from a HTTP client
/// Server the client is connected to
/// Socket of the connected client
public ClientConnection(HttpServer server, Socket socket) :
this(server, socket, DefaultBufferSize) {
this.socket = socket;
}
/// Initializes a new connection from a HTTP client
/// Server the client is connected to
/// Socket of the connected client
/// Size of the receive buffer
public ClientConnection(HttpServer server, Socket socket, int bufferSize) :
base(socket, bufferSize) {
this.server = server;
this.parser = new RequestParser(1024);
Start();
}
/// Drops the client from the server
public void Drop() {
try {
this.server.NotifyClientDisconnected(this);
}
finally {
Shutdown();
}
}
/// Called when the connection has been dropped by the peer
protected virtual void OnPeerDisconnected() {
this.server.NotifyClientDisconnected(this);
}
/// Called whenever data is received on the socket
/// Buffer containing the received data
/// Number of bytes that have been received
protected override void OnDataReceived(byte[] buffer, int receivedByteCount) {
parseRequest(buffer, receivedByteCount);
}
/// Processes the provided request and generates a server response
/// Request to be processed by the server
/// The response to the server request
protected virtual Response ProcessRequest(Request request) {
#if true // GENERATE_DUMMY_RESPONSE
Console.WriteLine(
DateTime.Now.ToString() + " Processed request for " + request.Uri
);
// Here's the HTML document we want to send to the client
MemoryStream messageMemory = new MemoryStream();
StreamWriter writer = new StreamWriter(messageMemory, Encoding.UTF8);
writer.WriteLine("");
writer.WriteLine("
Hello World");
writer.WriteLine("Hello World from the Nuclex Web Server");
writer.WriteLine("");
writer.Flush();
// Prepare the response message
Response theResponse = new Response(StatusCode.S200_OK);
// Add some random headers web server's like to provider
theResponse.Headers.Add("Cache-Control", "private");
theResponse.Headers.Add("Content-Type", "text/html; charset=UTF-8");
theResponse.Headers.Add("Date", "Wed, 30 Jul 2008 14:01:06 GMT");
theResponse.Headers.Add("Server", "Nuclex");
// Attach the HTML document to our response
theResponse.AttachStream(messageMemory);
// Now comes the important part, specify how many bytes we're going to
// transmit.
// TODO: This should be done by parseRequest()
// Whether it's needed or not depends on the transport protocol used
messageMemory.Position = 0;
theResponse.Headers.Add("Content-Length", messageMemory.Length.ToString());
#endif
return theResponse;
}
/// Parses incoming data into an HTTP request
/// Buffer containing the received data
/// Number of bytes in the receive buffer
private void parseRequest(byte[] buffer, int receivedByteCount) {
ArraySegment data = new ArraySegment(buffer, 0, receivedByteCount);
while(data.Count > 0) {
Response response; // Response that will be delivered to the client
bool dropConnection = false; // Whether to close the connection
try {
// Try to parse a complete request from the bytes the client has sent to us.
// If there isn't enough data available yet, we exit here and hopefully the
// next time data arrives it will be enough to complete the request entity.
Request request = this.parser.ProcessBytes(data.Array, data.Offset, data.Count);
if(request == null) {
break;
}
// We've got an actual request. Now let's handle it using the implementation
// provided by the deriving class from the user!
response = ProcessRequest(request);
}
catch(Exceptions.HttpException httpException) {
response = new Response(httpException.StatusCode, httpException.Message);
dropConnection = true;
}
catch(Exception exception) {
response = new Response(StatusCode.S500_Internal_Server_Error, exception.Message);
dropConnection = true;
}
// Transform the response entity into a stream of bytes and send it back to
// the client. If any additional data has to be transmitted, this will be handled
// by the attachment transmitter.
byte[] responseBytes = ResponseFormatter.Format(response);
this.socket.Send(responseBytes); // TODO: Can get an ObjectDisposedException here!
// TODO: Evil hack!
if(response.AttachedStream != null) {
this.socket.Send(((MemoryStream)response.AttachedStream).GetBuffer());
}
// TODO: Respect the keep-alive request header here
// If keep-alive is not set, we should close the connection here!
if(dropConnection) {
Drop();
} else {
// Now take the remaining data out of the parser and bring the parser back into
// its initial state so it can begin parsing the next request
data = this.parser.GetRemainingData();
this.parser.Reset();
}
}
}
/// Server to which the client is connected
private HttpServer server;
/// HTTP request parser we use for interpreting client requests
private RequestParser parser;
/// Socket the client is connected by
private Socket socket;
}
} // namespace Nuclex.Networking.Http