singrdk/base/Applications/MapPointProxy/HttpRequest.cs

698 lines
24 KiB
C#
Raw Permalink Normal View History

2008-11-17 18:29:00 -05:00
// ----------------------------------------------------------------------------
2008-03-05 09:52:00 -05:00
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
2008-11-17 18:29:00 -05:00
// ----------------------------------------------------------------------------
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
//
// This helper class assists in forming HTTP requests and retrieving the
// resulting data.
//
2008-03-05 09:52:00 -05:00
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class HttpRequest
{
private string fRequestUri, fHost, fResource, fMethod, fContentType;
private int fHostPort;
private byte[] fRequestData;
private Hashtable fRequestHeaders;
private long fTimeout; // Zero for infinite
// CONSTANTS
private static string proxyHost = null;
private static int proxyPort = 0;
const int readIncrementSize = 1024 * 2; // 2Kbytes
const int bodySizeGuess = 5 * 1024; // 5KBytes
public static void ConfigureProxy(string host, int port)
{
proxyHost = host;
proxyPort = port;
}
public HttpRequest(string! uri)
{
fRequestUri = uri;
// Parse the URI into a host-part and a path-part.
const string httpPrefix = "http://";
2008-11-17 18:29:00 -05:00
if (!uri.StartsWith(httpPrefix)) {
2008-03-05 09:52:00 -05:00
throw new ArgumentException("URI must begin with 'http://'");
}
int startIndex = httpPrefix.Length;
int firstSlash = uri.IndexOf('/',startIndex);
string fHost;
2008-11-17 18:29:00 -05:00
if (firstSlash != -1) {
2008-03-05 09:52:00 -05:00
fHost = uri.Substring(startIndex, firstSlash - startIndex);
fResource = uri.Substring(firstSlash);
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// Host is the entire Uri
fHost = uri.Substring(startIndex);
fResource = String.Empty;
}
int firstColon = fHost.IndexOf(':');
2008-11-17 18:29:00 -05:00
if (firstColon != -1) {
2008-03-05 09:52:00 -05:00
fHostPort = Int32.Parse(fHost.Substring(firstColon + 1));
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
fHostPort = 80; // default
}
this.fRequestHeaders = new Hashtable();
this.fMethod = "GET";
this.fHost = fHost;
}
public void AddHeader(string! headerName, string headerValue)
{
fRequestHeaders[headerName] = headerValue;
}
public string Method
{
get { return fMethod; }
set { fMethod = value; }
}
public string ContentType
{
get { return fContentType; }
set { fContentType = value; }
}
public byte[] RequestData
{
get { return fRequestData; }
set { fRequestData = value; }
}
public long Timeout
{
get { return fTimeout; }
set { fTimeout = value; }
}
public string Resource
{
get { return fResource; }
}
// Perform the HTTP hit and return the result!
public HttpResponse GetResponse()
{
//
// Step 1: Connect to the remote server
//
Socket httpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
ProtocolType.Tcp);
string serverName, requestUri;
int serverPort;
2008-11-17 18:29:00 -05:00
if (proxyPort > 0) {
2008-03-05 09:52:00 -05:00
serverName = proxyHost;
serverPort = proxyPort;
requestUri = fRequestUri; // Use the full URI
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
serverName = fHost;
serverPort = fHostPort;
requestUri = fResource; // Use just the resource
}
IPAddress serverAddress;
2008-11-17 18:29:00 -05:00
try {
2008-03-05 09:52:00 -05:00
serverAddress = IPAddress.Parse(serverName);
}
2008-11-17 18:29:00 -05:00
catch (Exception) {
2008-03-05 09:52:00 -05:00
// Hostname didn't parse as an IP address; try to resolve it
IPHostEntry! entry = (!)Dns.Resolve(serverName);
IPAddress[]! addresses = (!)entry.AddressList;
2008-11-17 18:29:00 -05:00
if (addresses.Length == 0) {
2008-03-05 09:52:00 -05:00
throw new Exception("Couldn't resolve host name");
}
serverAddress = addresses[0];
}
IPEndPoint serverEndpoint = new IPEndPoint(serverAddress, serverPort);
// Connect to the remote server
httpSocket.Connect(serverEndpoint);
//
// Step 2: Formulate and transmit the request line and any supporting data
//
// Send the request line
string requestLine = fMethod + " " + requestUri + " HTTP/1.1\r\nHost: " +
fHost + "\r\nConnection: Close\r\n";
// Send an indication of the request data content type and size, if appropriate
2008-11-17 18:29:00 -05:00
if (fRequestData != null) {
2008-03-05 09:52:00 -05:00
requestLine += "Content-Length: " + fRequestData.Length + "\r\n" +
"Content-Type: " + fContentType + "\r\n";
}
// Add any user-specified headers
2008-11-17 18:29:00 -05:00
foreach (string! headerName in fRequestHeaders.Keys) {
2008-03-05 09:52:00 -05:00
requestLine += headerName + ": " + fRequestHeaders[headerName] + "\r\n";
}
requestLine += "\r\n";
byte[] requestBytes = Encoding.ASCII.GetBytes(requestLine);
httpSocket.Send(requestBytes);
// Send any request data
2008-11-17 18:29:00 -05:00
if (fRequestData != null) {
2008-03-05 09:52:00 -05:00
httpSocket.Send(fRequestData);
}
// Signal we're done sending
httpSocket.Shutdown(SocketShutdown.Send);
//
// Step 3: Parse the response line and headers
//
// Pump the response into an HttpHeadersParser
ByteBuffer scratchBuffer = new ByteBuffer();
HttpHeadersParser headerParser = new HttpHeadersParser(scratchBuffer);
bool doneWithout100Continue = false, doneWithHeaders = false;
int nextWritePos = 0;
HttpResponse response = null;
do
{
2008-11-17 18:29:00 -05:00
while (!doneWithHeaders) {
2008-03-05 09:52:00 -05:00
// Make 2K available for each read
scratchBuffer.EnsureSize(nextWritePos + readIncrementSize);
int numReadBytes = httpSocket.Receive(scratchBuffer.UnderlyingBuffer, nextWritePos,
scratchBuffer.Size - nextWritePos, SocketFlags.None);
2008-11-17 18:29:00 -05:00
if (numReadBytes == 0) {
2008-03-05 09:52:00 -05:00
// We ran out of data before we finished parsing headers.
throw new Exception("Unexpected end of data");
}
nextWritePos += numReadBytes;
2008-11-17 18:29:00 -05:00
if (headerParser.Pump(nextWritePos)) {
2008-03-05 09:52:00 -05:00
// Finished parsing headers!
response = headerParser.GetResponse();
doneWithHeaders = true;
}
}
2008-11-17 18:29:00 -05:00
if (response == null) {
2008-03-05 09:52:00 -05:00
throw new Exception("HTTP response data unexpectedly null");
}
// Special case: if we see a 100-continue, we want to carefully start
// over at the next chunk of data!
2008-11-17 18:29:00 -05:00
if (response.StatusCode == 100) {
2008-03-05 09:52:00 -05:00
// Switch to a new scratch buffer that will contain the data
// following the 100-continue.
int remainderBeginning = headerParser.BodyDataOffset;
int remainderLength = nextWritePos - remainderBeginning;
ByteBuffer newScratch = new ByteBuffer();
ByteBuffer oldScratch = scratchBuffer;
// Reset parsing by creating a new parser
headerParser = new HttpHeadersParser(newScratch);
// Switch over!
scratchBuffer = newScratch;
nextWritePos = remainderLength;
2008-11-17 18:29:00 -05:00
if (remainderLength > 0) {
2008-03-05 09:52:00 -05:00
// Copy the beginning of the next data chunk into newScratch
newScratch.EnsureSize(remainderLength);
Buffer.BlockCopy(oldScratch.UnderlyingBuffer, remainderBeginning,
newScratch.UnderlyingBuffer, 0, remainderLength);
// Make sure the fragment we were already holding gets pumped in
2008-11-17 18:29:00 -05:00
if (headerParser.Pump(nextWritePos)) {
2008-03-05 09:52:00 -05:00
// Interestingly, we're already done
response = headerParser.GetResponse();
2008-11-17 18:29:00 -05:00
if (response == null) {
2008-03-05 09:52:00 -05:00
throw new Exception("HTTP response data unexpectedly null");
}
Debug.Assert(response.StatusCode != 100);
doneWithout100Continue = true;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// Go back and carry on
doneWithHeaders = false;
}
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// The chunk we were chewing on exactly contained the 100-continue,
// so we definitely need to go back and read some more...
doneWithHeaders = false;
}
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// The response was not 100-continue, so we're all done
doneWithout100Continue = true;
}
}
2008-11-17 18:29:00 -05:00
while (!doneWithout100Continue);
2008-03-05 09:52:00 -05:00
//
// Step 4: Process the body data
//
// Now figure out how we're going to deal with the actual body
// of the response. We almost certainly already have an initial
// chunk of the body data in the scratchBuffer, since we read the
// headers in 2K chunks
//
int bodyScratchBeginning = headerParser.BodyDataOffset;
int scratchBodyLength = nextWritePos - bodyScratchBeginning;
string transferEncoding = response.GetHeader("Transfer-Encoding");
if (transferEncoding != null &&
transferEncoding.ToLower().Equals("chunked"))
{
// Chunked encoding is special: the beginning of the body data
// already specifies some chunk information. Run this through our
// chunk decoder to straighten it out.
byte[] initialBodyChunk = scratchBuffer.TrimAndCopy(bodyScratchBeginning, scratchBodyLength);
ChunkedEncodingParser chunkParser = new ChunkedEncodingParser(initialBodyChunk, httpSocket);
ByteBuffer bodyData = chunkParser.Run();
response.BodyData = bodyData.TrimAndCopy(bodyData.Size);
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
ByteBuffer bodyBuffer;
// Non-chunked encoding. First, move the beginning of the body
// data to a new ByteBuffer for sanity.
// Separate out any initial body data into a new ByteBuffer, for sanity
int bodySize = (response.ContentLength > 0) ? response.ContentLength : bodySizeGuess;
// Hard to imagine how this would happen...
2008-11-17 18:29:00 -05:00
if (bodySize < scratchBodyLength) {
2008-03-05 09:52:00 -05:00
bodySize = scratchBodyLength;
}
bodyBuffer = new ByteBuffer(bodySize);
Buffer.BlockCopy(scratchBuffer.UnderlyingBuffer, bodyScratchBeginning,
bodyBuffer.UnderlyingBuffer, 0, scratchBodyLength);
int nextBodyPos = scratchBodyLength;
2008-11-17 18:29:00 -05:00
if (response.ContentLength > 0) {
2008-03-05 09:52:00 -05:00
// Read until we've gotten exactly the expected number of bytes
int bodyDataLeft = response.ContentLength - scratchBodyLength;
2008-11-17 18:29:00 -05:00
while (bodyDataLeft > 0) {
2008-03-05 09:52:00 -05:00
int numReadBytes = httpSocket.Receive(bodyBuffer.UnderlyingBuffer, nextBodyPos,
bodyDataLeft, SocketFlags.None);
2008-11-17 18:29:00 -05:00
if (numReadBytes == 0) {
2008-03-05 09:52:00 -05:00
throw new Exception("Connection closed unexpectedly");
}
nextBodyPos += numReadBytes;
bodyDataLeft -= numReadBytes;
}
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// No indicated ContentLength; Just read until the connection gets closed!
int numReadBytes = 0;
// Read until the remote side closes
do
{
bodyBuffer.EnsureSize(bodyBuffer.Size + readIncrementSize);
numReadBytes = httpSocket.Receive(bodyBuffer.UnderlyingBuffer, nextBodyPos,
bodyBuffer.Size - nextBodyPos, SocketFlags.None);
nextBodyPos += numReadBytes;
}
while (numReadBytes > 0);
}
response.BodyData = bodyBuffer.TrimAndCopy(nextBodyPos);
}
// All done!
httpSocket.Close();
return response;
}
private class HttpHeadersParser
{
// The buffer we are chewing on
ByteBuffer fBuffer;
// The response we are forming
private HttpResponse fResponse;
private int fContentLength;
// Working state
private State fState;
private int fPosition;
private int fBufferLimit; // One more than the last usable index in the buffer
private enum State
{
BeforeStatusLine,
ParsingHeaders,
Complete
}
public HttpHeadersParser(ByteBuffer buffer)
{
fBuffer = buffer;
fResponse = new HttpResponse();
}
public bool Pump(int newBufferLimit)
{
bool allDone = false, outOfRoom = false;
2008-11-17 18:29:00 -05:00
while ((!allDone) && (!outOfRoom)) {
2008-03-05 09:52:00 -05:00
PumpInternal(newBufferLimit, out allDone, out outOfRoom);
}
return allDone;
}
private void PumpInternal(int newBufferLimit, out bool allDone, out bool outOfRoom)
{
fBufferLimit = newBufferLimit;
2008-11-17 18:29:00 -05:00
if (fState == State.Complete) {
2008-03-05 09:52:00 -05:00
// We've already completed
allDone = true;
outOfRoom = false;
return;
}
int CRLFOffset = FindNextCRLF(fPosition, fBufferLimit);
2008-11-17 18:29:00 -05:00
if (fState == State.BeforeStatusLine) {
if (CRLFOffset != -1) {
2008-03-05 09:52:00 -05:00
HandleStatusLine(fPosition, CRLFOffset - fPosition);
fState = State.ParsingHeaders;
fPosition = CRLFOffset + 2;
allDone = false;
outOfRoom = false;
return;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// We couldn't see another CRLF before the end of the
// usable part of the buffer, so we're out of room.
allDone = false;
outOfRoom = true;
return;
}
}
2008-11-17 18:29:00 -05:00
else if (fState == State.ParsingHeaders) {
2008-03-05 09:52:00 -05:00
// Special case: if the unprocessed part starts with CRLF it
// means we are looking at the end of the headers, since we consume
// the trailing CRLF on a single header line when parsing.
2008-11-17 18:29:00 -05:00
if (CRLFOffset == fPosition) {
2008-03-05 09:52:00 -05:00
fState = State.Complete;
fPosition += 2;
allDone = true;
outOfRoom = false;
return;
}
2008-11-17 18:29:00 -05:00
else if (CRLFOffset != -1) {
2008-03-05 09:52:00 -05:00
HandleHeaderLine(fPosition, CRLFOffset - fPosition);
fPosition = CRLFOffset + 2;
allDone = false;
outOfRoom = false;
return;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// We couldn't see another CRLF before the end of the
// usable part of the buffer, so we're out of room.
allDone = false;
outOfRoom = true;
return;
}
}
// Someone must have added a state or mucked up the code
allDone = false;
outOfRoom = false;
Debug.Assert(false);
}
public HttpResponse GetResponse()
{
2008-11-17 18:29:00 -05:00
if (fState == State.Complete) {
2008-03-05 09:52:00 -05:00
return fResponse;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// Not allowed to have half-baked results!
return null;
}
}
public int BodyDataOffset
{
get
{
2008-11-17 18:29:00 -05:00
if (fState == State.Complete) {
2008-03-05 09:52:00 -05:00
return fPosition;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// No answer if we're not done processing
return -1;
}
}
}
public int ContentLength
{
get
{
2008-11-17 18:29:00 -05:00
if (fState == State.Complete) {
2008-03-05 09:52:00 -05:00
return fContentLength;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// Not available if we're not done processing
return -1;
}
}
}
private void HandleStatusLine(int startOffset, int length)
{
string status = Encoding.ASCII.GetString(fBuffer.UnderlyingBuffer, startOffset, length);
const string HTTP11 = "HTTP/1.1 ";
2008-11-17 18:29:00 -05:00
if (!status.StartsWith(HTTP11)) {
2008-03-05 09:52:00 -05:00
throw new Exception("Response was not HTTP/1.1");
}
int secondSpace = status.IndexOf(' ', HTTP11.Length);
string statusString;
2008-11-17 18:29:00 -05:00
if (secondSpace == -1) {
2008-03-05 09:52:00 -05:00
// Assume this means there is a status code with no explanation string
statusString = status.Substring(HTTP11.Length);
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// Grab just the status code
statusString = status.Substring(HTTP11.Length, secondSpace - HTTP11.Length);
}
int statusCode = Int32.Parse(statusString);
fResponse.StatusCode = statusCode;
}
private void HandleHeaderLine(int startOffset, int length)
{
string header = Encoding.ASCII.GetString(fBuffer.UnderlyingBuffer, startOffset, length);
int firstColon = header.IndexOf(':');
2008-11-17 18:29:00 -05:00
if (firstColon == -1) {
2008-03-05 09:52:00 -05:00
throw new Exception("Malformed header string");
}
string headerName = header.Substring(0, firstColon);
string headerValue = header.Substring(firstColon + 1).Trim();
2008-11-17 18:29:00 -05:00
if (headerName.ToLower().Equals("content-length")) {
2008-03-05 09:52:00 -05:00
fContentLength = Int32.Parse(headerValue);
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
fResponse.AddHeader(headerName, headerValue);
}
}
private int FindNextCRLF(int startOffset, int limitOffset)
{
int offset = startOffset;
2008-11-17 18:29:00 -05:00
while (offset < limitOffset) {
if (fBuffer[offset] == (byte)'\r') {
if ((offset + 1 < limitOffset) && (fBuffer[offset + 1] == (byte)'\n')) {
2008-03-05 09:52:00 -05:00
return offset;
}
}
offset++;
}
return -1;
}
}
private class ChunkedEncodingParser
{
// This buffer is what we start with; it has an initial
// fragment of the body data
private byte[] fInitialFragment;
private int fFragmentOffset; // How far into fInitialFragment we are
private Socket fSocket; // Socket to read additional data from
private byte[] fSingleByte;
public ChunkedEncodingParser(byte[] initialFragment, Socket readSocket)
{
fInitialFragment = initialFragment;
fSocket = readSocket;
fSingleByte = new byte[1];
}
public ByteBuffer! Run()
{
ByteBuffer bodyData = new ByteBuffer();
int chunkSize = 0;
2008-11-17 18:29:00 -05:00
while (true) {
2008-03-05 09:52:00 -05:00
ByteBuffer! chunkLine = ReadToCRLF();
string! chunkString = (!)Encoding.ASCII.GetString(chunkLine.UnderlyingBuffer, 0, chunkLine.Size);
chunkString = (!)chunkString.Trim();
int firstSemi = chunkString.IndexOf(';');
2008-11-17 18:29:00 -05:00
if (firstSemi != -1) {
2008-03-05 09:52:00 -05:00
// Use only the portion before the semicolon
chunkSize = Int32.Parse(chunkString.Substring(0, firstSemi),
NumberStyles.AllowHexSpecifier);
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// No semicolon; use the entire line
chunkSize = Int32.Parse(chunkString, NumberStyles.AllowHexSpecifier);
}
2008-11-17 18:29:00 -05:00
if (chunkSize == 0) {
2008-03-05 09:52:00 -05:00
// We're done! Don't bother reading the trailer
return bodyData;
}
// Read in the amount indicated by the chunk header. It may take multiple passes
// to do this.
int writeOffset = bodyData.Size;
bodyData.EnsureSize(bodyData.Size + chunkSize);
2008-11-17 18:29:00 -05:00
while (chunkSize > 0) {
2008-03-05 09:52:00 -05:00
int numBytesRead = MassRead(bodyData.UnderlyingBuffer, writeOffset, chunkSize);
chunkSize -= numBytesRead;
writeOffset += numBytesRead;
}
// Peel off the CRLF that trails a chunk
ByteBuffer emptyLine = ReadToCRLF();
2008-11-17 18:29:00 -05:00
if (emptyLine.Size != 0) {
2008-03-05 09:52:00 -05:00
throw new Exception("Malformed HTTP/1.1 chunk -- no trailing lone CRLF");
}
}
}
private ByteBuffer! ReadToCRLF()
{
ByteBuffer retval = new ByteBuffer();
InnerReadToCRLF(retval);
return retval;
}
private void InnerReadToCRLF(ByteBuffer buffer) // buffer can be null
{
// Read characters into buffer until we see a CRLF (don't include)
byte nextByte = GetNextByte();
2008-11-17 18:29:00 -05:00
while (true) {
if (nextByte == (byte)'\r') {
2008-03-05 09:52:00 -05:00
nextByte = GetNextByte();
2008-11-17 18:29:00 -05:00
if (nextByte == (byte)'\n') {
2008-03-05 09:52:00 -05:00
// Done
return;
}
2008-11-17 18:29:00 -05:00
else {
if (buffer != null) {
buffer.Add((byte)'\r');
}
2008-03-05 09:52:00 -05:00
// Don't read again; nextByte might
// be the first byte of CRLF.
}
}
2008-11-17 18:29:00 -05:00
else {
if (buffer != null) {
buffer.Add(nextByte);
}
2008-03-05 09:52:00 -05:00
nextByte = GetNextByte();
}
}
}
private void DiscardToCRLF()
{
InnerReadToCRLF(null);
}
private byte GetNextByte()
{
2008-11-17 18:29:00 -05:00
if (fFragmentOffset < fInitialFragment.Length) {
2008-03-05 09:52:00 -05:00
return fInitialFragment[fFragmentOffset++];
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
int received = fSocket.Receive(fSingleByte, 0, 1, SocketFlags.None);
2008-11-17 18:29:00 -05:00
if (received == 1) {
2008-03-05 09:52:00 -05:00
return fSingleByte[0];
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
throw new Exception("Connection closed unexpectedly");
}
}
}
private int MassRead(byte[] buffer, int offset, int maxLength)
{
2008-11-17 18:29:00 -05:00
if (fFragmentOffset < fInitialFragment.Length) {
2008-03-05 09:52:00 -05:00
// Still data left in the original fragment; return as much
// of that as possible.
int initialFragmentLeft = fInitialFragment.Length - fFragmentOffset;
int amountToUse = maxLength <= initialFragmentLeft ? maxLength : initialFragmentLeft;
Buffer.BlockCopy(fInitialFragment, fFragmentOffset, buffer, offset, amountToUse);
fFragmentOffset += amountToUse;
return amountToUse;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// No data left in the initial fragment; read from the network.
return fSocket.Receive(buffer, offset, maxLength, SocketFlags.None);
}
}
}
}