7

The issue I'm currently facing is that I'm unable to get my C# program to write the file I want to send in the response (I'm writing the bytes of the file) and have a valid HTTP response, the browser I'm testing it with is saying ERR_INVALID_HTTP_RESPONSE if I have appended the file to the response.

Originally I was using a HttpListener but this was requiring me to run my IDE as admin every time so I decided to move away from it and start using a TcpListener.

This is just a simple web server that is running entirely off of C# and utilizes the TcpListener from System.Net. So far I've tried using the BaseStream from the StreamWriter to write the bytes of the file into the response.

I have been searching around to look for other ways to send a file via TCP / HTTP however it seems two things are apparent, its not brilliantly documented on how to properly do it and nobody seems to have asked the same question for this specific scenario.

So far the code that handles the response to the incoming connection is as follows.

      StreamReader sr = new StreamReader(client.GetStream());
      StreamWriter sw = new StreamWriter(client.GetStream());
      string request = sr.ReadLine();
      if (request != null) {
        //byte[] testData = Encoding.UTF8.GetBytes("Test");
        string[] tokens = request.Split(" ");
        try {
          FileInfo file = files[tokens[1]];
          byte[] fileBytes = File.ReadAllBytes(file.FullName);
          sw.WriteLine("HTTP/1.0 200 OK\n");
          sw.BaseStream.Write(fileBytes, 0, fileBytes.Length);
          sw.Flush();
        } catch {
          sw.WriteLine("HTTP/1.0 404 OK\n");
          sw.Write("<h1>Error 404</h1><p>The file you were looking for does not exist</p>");
          sw.Flush();
        }
      }
      client.Close();

The expected result is that the browser will show the image much like you'd see from an imgur link, basically the same format as I will use to show the screenshot of the current result is the expected result.

image taken from https://i.stack.imgur.com/0wnrt.png

questionasker
  • 2,536
  • 12
  • 55
  • 119
AlexJBallz
  • 77
  • 6
  • 5
    `404 OK`?!?! That seems a bit off. – Kenneth K. May 14 '19 at 13:53
  • It might well be but it worked just fine when I tested trying to open a file that doesn't exist – AlexJBallz May 14 '19 at 13:57
  • 2
    Well, browsers only look at the `404` part; the `OK` is more for humans. But technically speaking it should be `Not Found` for a `404`. – Kenneth K. May 14 '19 at 13:59
  • The reason "because it requires admin right" why you switched from HTTP Listener to TCP listener is not enough to implement your own webserver. Unless you want to spend days implementing http standard request and responses, you better stick to http listener. Much easier to solve is the problem with admin rights and http listener: https://stackoverflow.com/questions/4019466/httplistener-access-denied – Harry May 14 '19 at 13:59
  • That is subject to opinion @Harry. I literally only need one function for it, its not going to take me days to implement standard requests and responses because they simply don't need to be there for what I'm using it for. – AlexJBallz May 14 '19 at 14:08
  • @AlexJBallz, i believe you are doing this the first time. As long as you don't write the client yourself but need to support different clients like standard browsers, you will spend lots of time supporting all the unexpected behaviour. – Harry May 14 '19 at 14:11
  • @Harry For the use this webserver will get, that support seriously just isn't needed. – AlexJBallz May 14 '19 at 14:14

1 Answers1

6

HTTP dictates that initial lines and headers should end in CRLF (not LF), and that a double CRLF should separate initial lines and headers from the response body.

Here's a super-handy quick-guide to the HTTP protocol:

https://www.jmarshall.com/easy/http/

You'll also probably need to flush your StreamWriter before you switch from operating over the StreamWriter to operating over it's base stream.

sw.Write("HTTP/1.0 200 OK\r\n\r\n");
sw.Flush(); //  <-- HERE
sw.BaseStream.Write(fileBytes, 0, fileBytes.Length);

I'd also avoid WriteLine as the line endings are not guaranteed to be the same across all platforms.

spender
  • 117,338
  • 33
  • 229
  • 351
  • Thank you! Made progress, it now downloads the file rather than displaying it, I should be able to work out the rest from here. – AlexJBallz May 14 '19 at 14:05
  • 1
    @AlexJBallz Glad to help. I've added a short para about the wisdom of using `sw.Write` over `sw.WriteLine` – spender May 14 '19 at 14:19
  • 1
    @AlexJBallz You also need to be very careful to stick to ASCII in the initial/header portions of the transaction. – spender May 14 '19 at 14:54
  • Thanks for all your help, I've now reached my desired result :) – AlexJBallz May 14 '19 at 15:16