-1

My problem is probably stupid, but I can't figure out the solution by myself, so I hope someone here can help me.

I am trying to make a dll file for hosting a server on the localhost. I thought I did everything right, but I receive this error:

System.IndexOutOfRangeException: Index was outside the bounds of the array.

at minihttpc.Requests.HttpRequest.ParseHeaders(String[] requestLine) in C:\Users\deqnb\OneDrive\minihttpc\minihttpc\Requests\HttpRequest.cs:line 87
at minihttpc.Requests.HttpRequest.ParseRequest(String requestString) in C:\Users\deqnb\OneDrive\minihttpc\minihttpc\Requests\HttpRequest.cs:line 44
at minihttpc.Requests.HttpRequest..ctor(String requestString) in C:\Users\deqnb\OneDrive\minihttpc\minihttpc\Requests\HttpRequest.cs:line 21
at MiniServerHTTP.WebServer.ConnectionHandler.ReadRequest() in C:\Users\deqnb\OneDrive\minihttpc\MiniServerHTTP.WebServer\ConnectionHandler.cs:line 80
at MiniServerHTTP.WebServer.ConnectionHandler.ProcessRequest() in C:\Users\deqnb\OneDrive\minihttpc\MiniServerHTTP.WebServer\ConnectionHandler.cs:line 28

Here is my code - HttpRequest.cs:

using minihttpc.Common.CoreValidator;
using minihttpc.Common.GlobalConstants;
using minihttpc.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace minihttpc.Requests
{
    public class HttpRequest:IHttpRequest
    {
        public HttpRequest(string requestString)
        {
            CoreValidator.ThrowIfNullOrEmpty(requestString, nameof(requestString));

            this.FormData = new Dictionary<string, object>();
            this.QueryData = new Dictionary<string, object>();
            this.Headers = new HttpHeaderCollection();

            this.ParseRequest(requestString);
        }

        public string Path { get; private set; }
        public string Url { get; private set; }
        public Dictionary<string,object>FormData { get; }
        public Dictionary<string, object> QueryData { get; }
        public IHttpHeaderCollection Headers { get; private set; }
        public HttpRequestMethod RequestMethod { get; private set; }

        public void ParseRequest(string requestString)
        {
            string[] splitRequestContent = requestString.Split(new[] { 
GlobalConstants.HttpNewLine }, StringSplitOptions.None);
            string[] requestLine = splitRequestContent[0].Trim().Split(new[] 
{ ' ' }, StringSplitOptions.RemoveEmptyEntries);

            if (!this.IsValidReqiestLine(requestLine))
            {
                throw new BadRequestException();
            }

            this.ParseRequestMethod(requestLine);
            this.ParseRequestUrl(requestLine);
            this.ParseRequestPath();
            this.ParseHeaders(splitRequestContent.Skip(1).ToArray());
            //this.ParseRequestQueryParameters();
            this.ParseRequestParameters(splitRequestContent[splitRequestContent.Length - 1]);
        }

        bool IsValidRequestLine(string[] requestLine)
        {
            if (requestLine.Count() != 3 && requestLine[2] != "HTTP/1.1")
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        void ParseRequestMethod(string[] requestLine)
        {
            switch (requestLine[0])
            {
                case "GET": RequestMethod = HttpRequestMethod.Get; break;
                case "POST": RequestMethod = HttpRequestMethod.Post; break;
                case "PUT": RequestMethod = HttpRequestMethod.Put; break;
                case "DELETE": RequestMethod = HttpRequestMethod.Delete; break;
            }
        }

        void ParseRequestUrl(string [] requestLine)
        {
            this.Url = requestLine[1];
        }

        void ParseRequestPath()
        {
            this.Path = this.Url.Split("?").Take(1).First().ToString();
        }

        void ParseHeaders(string [] requestLine)
        {
            foreach(var line in requestLine)
            {
                Console.WriteLine(line); //a lot of info about the req
                if (line == GlobalConstants.HttpNewLine) break;

                string[] header = line.Split(' ').ToArray();
                //Console.WriteLine(header[1]);
                Headers.AddHeader(new HttpHeader(header[0],
 header[1]));//seems fine //line 87
            }

            if (Headers.ContainsHeader("host"))
            {
                throw new BadRequestException();
            }
        }

        void ParseRequestQueryParameters()
        {
            if (!(this.Url.Split('?').Length > 1)) return;
            this.Url.Split('?', '#')[1].Split('&').Select(plainQueryParameter => 
plainQueryParameter.Split());//not finished !!!!
        }

        void ParseFormDataParameters(string formData)
        {
            if (!string.IsNullOrEmpty(formData))
            {
                formData.Split('&').Select(plainQueryParameter => 
plainQueryParameter.Split('=')).ToList().ForEach(queryParameterKeyValue =>
                this.FormData.Add(queryParameterKeyValue[0], 
queryParameterKeyValue[1]));
            }
        }

        void ParseRequestParameters(string formData)//not being called
        {
            ParseRequestQueryParameters();
            ParseFormDataParameters(formData);
        }
    }
}

ConnectionHandler.cs:

using minihttpc.Common.CoreValidator;
using minihttpc.Requests;
using MiniServerHTTP.WebServer.Results;
using MiniServerHTTP.WebServer.Routing;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace MiniServerHTTP.WebServer
{
    public class ConnectionHandler
    {
        private readonly Socket client;
        private readonly IServerRoutingTable table;

        public ConnectionHandler(Socket client, IServerRoutingTable table)
        {
            CoreValidator.ThrowIfNull(client, nameof(client));
            CoreValidator.ThrowIfNull(client, nameof(client));
            this.client = client;
            this.table = table;
        }

        public async Task ProcessRequest()
        {
            try
            {
                var httpRequest = await this.ReadRequest();

                if (httpRequest != null)
                {
                    Console.WriteLine($"Processing: {httpRequest.RequestMethod} {httpRequest.Path}...");
                    var httpResponse = this.HandleRequest((IHttpRequest)httpRequest);
                    this.ProcessResponse(httpResponse);
                }
            }
            catch (BadRequestException e)//400
            {
                this.ProcessResponse(new TextResult(e.ToString(), 
HttpResponseStatusCode.BadRequest));
            }
            catch (Exception e)//500
            {
                this.ProcessResponse(new TextResult(e.ToString(), 
HttpResponseStatusCode.InternalServerError));
            }

            this.client.Shutdown(SocketShutdown.Both);
        }

        private async Task ProcessResponse(IHttpResponse httpResponse)
        {
            byte[] byteSegments = httpResponse.GetBytes();
            await this.client.SendAsync(byteSegments, SocketFlags.None);
        }

        private IHttpResponse HandleRequest(IHttpRequest httpRequest)
        {
            if(!this.table.Contains(httpRequest.RequestMethod, 
httpRequest.Path))
            {
                return new TextResult($"Route with method {httpRequest.RequestMethod} and path \"{httpRequest.Path}\"not found.", 
HttpResponseStatusCode.NotFound);
            }

            return this.table.Get(httpRequest.RequestMethod, 
httpRequest.Path).Invoke(httpRequest);
        }

        private async Task<HttpRequest> ReadRequest()
        {
            var result = new StringBuilder();
            var data = new ArraySegment<byte>(new byte[1024]);

            while (true)
            {
                int numberOfBytes = await this.client.ReceiveAsync(data.Array, SocketFlags.None);
                if (numberOfBytes == 0) break;
                var bytesAsString = Encoding.UTF8.GetString(data.Array, 0,
 numberOfBytes);
                result.Append(bytesAsString);

                if (numberOfBytes < 1023) break;
            }

            if (result.Length == 0)
            {
                return null;
            }

            return new HttpRequest(result.ToString());
        }
    }
}

When I check the parameters manually and variables in minihttpc.Requests.HttpRequest.ParseHeaders(), everything seems fine. I can't get where the problem is.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Darg
  • 1
  • 2
  • 1
    When you debug, which exact line of code produces the exception? What are the exact observed runtime values of the variables used in that line of code? "When I check [...] everything seems good" isn't really a specific debugging step or observation. – David Nov 02 '22 at 16:10
  • 1
    If I count correctly, Line 87 is `new HttpHeader(header[0], header[1])`, right? So what is the value of `header` here? – Klaus Gütter Nov 02 '22 at 16:31
  • Could you edit the code and add a comment at the line 87? Like this: `// line 87`. Also it would be nice if you could insert line-breaks in the long lines of code, so that all the code is visible doing only vertical scrolling. Horizontal scrolling is a pain. – Theodor Zoulias Nov 02 '22 at 16:49
  • 1
    StriplingWarrior is correct. There was an empty line in the request. Anyway thank you all for the fast response. Theodor Zoulias, I will edit the question and will take a note for future questions. – Darg Nov 02 '22 at 18:20
  • @marc_s personally I find exception-stack-traces formatted as code more readable than formatted as a quote! – Theodor Zoulias Nov 11 '22 at 11:52

1 Answers1

2
string[] header = line.Split(' ').ToArray();
Headers.AddHeader(new HttpHeader(header[0], header[1]));

I can pretty much guarantee you're running into a line that doesn't have a space in it. Maybe there's a line that's empty, for example. That wouldn't be obvious from the Console.WriteLine() that you're doing, because empty lines are kind of invisible.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315