Advanced Code Golf - Write a small HTTP server

39

26

Your challenge is to write a code-golf HTTP server that accepts GET requests. It obviously doesn't have to be fully featured, but it must serve files from a directory.

Rules:

  • The HTTP server must listen on TCP port 36895 (0x901F)
  • It must serve files from /var/www on *NIX systems (e.g. Linux), or C:\hgolf on Windows.
  • You may ignore all incoming HTTP headers except the GET itself.
  • If the HTTP method is not GET, you must send back a status code of "405 Not Supported" and a body of "405 Not Supported".
  • If the file does not exist, you must send back a status code of "404 File Not Found" and a body of "404 File Not Found".
  • If the file exists but could not be read for some reason, you must send back a status code of "500 Server Error" and a body of "500 Server Error".
  • If the user requests / or any other existing directory root (e.g. /foo/ where a directory foo exists in /var/www/), respond with a blank page.
  • Your response must contain at least the minimum headers to allow the content to be displayed on Firefox 8.0 and Internet Explorer 8.0
  • You must respond with the Content-Type header set, but you only have to support extensions html => text/html and txt => text/plain. For any other file extension, Send application/octet-stream as the content type.
  • Your code must be able to transfer both ASCII and binary data, though you do not explicitly have to distinguish between the two.
  • You may not use 3rd party libraries.
  • You may not use in-built classes or features designed to process HTTP requests (e.g. HttpListener in C#)
  • If your code will only work on a specific OS due to the socket APIs you've used, please state this.

Solutions must include an image showing it serving a HTML page to a browser.

If you've got any questions, please feel free to ask! :)

Polynomial

Posted 2011-11-26T13:15:14.170

Reputation: 4 082

3This should include a big caveat: do not expose any of these solutions to a public network interface! Since the aim is code golf rather than security, they will be dangerously insecure. (E.g. I expect that all of them would allow .. in the path as a way of breaking out of the defined document root). – Peter Taylor – 2013-03-02T12:02:22.440

8Someone always invents a better fool. – Peter Taylor – 2013-03-02T15:11:48.043

21@PeterTaylor So I should stop using my solution to serve my blog? :-O – Gareth – 2013-03-02T18:33:53.067

is the http module in Node.js ok? – markasoftware – 2014-01-02T22:26:03.433

@Markasoftware You may not use in-built classes or features designed to process HTTP requests (e.g. HttpListener in C#) – nyuszika7h – 2014-06-22T10:41:07.860

Answers

13

Ruby, 383 characters

require 'socket'
v=TCPServer.new 36895
while c=v.accept
h="200 OK";m="";t=""
(c.gets=~/GET (\S*)/)?File.directory?(s='C:/hgolf'+$1)?0:File.exist?(s)?(h+="\r\nContent-Type: "+(s=~/\.txt$/?"text/plain":s=~/\.html$/?"text/html":"application/octet-stream");t="IO.copy_stream(s,c)"):(h=m="404 File Not Found"):(h=m="405 Not Supported")
c.puts "HTTP/1.1 #{h}\r\n\r\n"+m
eval(t)
c.close
end

The restructuring of the code to pack the essential logic into one line which makes the code shorter. Nevertheless, I hope this version can be golfed a little bit further.

enter image description here

Note: I didn't yet implement the "500 Server Error" because I couldn't find an appropriate structure (maybe packaging everything into begin/rescue/end but reading the file is done after the header was sent).

Howard

Posted 2011-11-26T13:15:14.170

Reputation: 23 109

8Without 500 Server Error it doesn't fit requirements so it should not be accepted. – Hynek -Pichi- Vychodil – 2013-04-19T17:45:00.963

Do you have a screenshot of it in action? :) – Polynomial – 2011-11-30T06:48:40.727

@Polynomial Added a screenshot. – Howard – 2011-11-30T18:35:01.220

26

Bash+netcat: 354 characters

  • Two script files are used.
  • No URL decoding, thus file names with spaces and special characters not supported.
  • Single threaded, so concurrent requests may fail.
  • Tested on Linux, works nicely with Firefox and Opera.

webserver.sh (33 characters):

while :;do nc -lp36895 -e./w;done

w (321 characters):

read m p v
p=/var/www$p
t=text/plain
[[ -r $p ]]||s='500 Server Error'
[[ -e $p ]]||s='404 Not Found'
[[ $m != GET ]]&&s='405 Not Supported'
[[ $s||-d $p ]]||case ${p##*.} in
txt);;html)t=text/html;;*)t=application/octet-stream;;esac
echo "$v ${s-200 Ok}
Content-Type:$t
"
[[ $s ]]&&echo $s||{ [[ -d $p ]]&&echo||cat $p;}

Sample run:

bash-4.2$ ./webserver.sh 

Sample visit (Firefox 19.0):

enter image description here

manatwork

Posted 2011-11-26T13:15:14.170

Reputation: 17 865

Netcat - it'll do anything! – Mr. Llama – 2013-03-04T15:17:31.777

Curious... whenever I try this nc tells me that the -e option is not supported. Any ideas? – tomsmeding – 2013-03-04T18:15:20.320

You probably have nc from BusyBox. I use GNU netcat.

– manatwork – 2013-03-04T18:36:37.380

1What about text/plain? – ugoren – 2013-03-06T07:44:55.113

What you mean @ugoren? Files with .txt extension and error pages are both served as text/plain. See here some tests: http://pastebin.com/ErNtvQzx (Ignore the read errors, they are usual reactions of the client netcat.)

– manatwork – 2013-03-06T07:58:02.043

Sorry, my mistake. I missed saw that text/plain is missing in the case, but didn't see it at the top. – ugoren – 2013-03-06T16:32:21.460

You scared me, @ugoren. I thought I missed a bug. I moved the text/plain separately to ensure that error pages and “directory index” pages also have Content-Type response header. – manatwork – 2013-03-06T16:55:58.213

webserver.sh can be golfed to for((;;)){ nc -lp36895 -e./w;} (30 chars) – None – 2014-06-22T20:07:13.483

Indeed. I read your related tip but had no time to apply it in my posts. Thank you, @professorfish.

– manatwork – 2014-06-23T09:52:06.837

13

Haskell, 636

import Data.List;import Network;import System.Directory;import System.IO
main=listenOn(PortNumber 36895)>>=l;l s=(n s`catch`\_->u())>>l s
n s=do(h,_,_)<-accept s;z h>>=q.words>>=hPutStr h;hClose h
q("GET":p:_)=d$"/var/www"++p;q _=c"405 Not Supported"
d p=doesFileExist p>>=f p;e x|x=u$s""""|1<3=c"404 File Not Found"
f p x|x=t p`catch`\_e->c"500 Server Error"|1<3=doesDirectoryExist p>>=e
t p=fmap(s$"\nContent-Type: "++g p)$z=<<openBinaryFile p ReadMode
r s h b=["HTTP/1.0 ",s,h,"\n\n",b]>>=id
g p|b".html"p="text/html"|b".txt"p="text/plain"|1<3="application/octet-stream"
s=r"200 OK";c s=u$r s[]s;u=return;b=isSuffixOf;z=hGetContents

Files are streamed lazily, so serving large files does not use much memory, but this also means that only errors at the start of transmission (e.g. permission errors) result in a 500 Server Error.

Serving a HTML page

Tested on Ubuntu 10.10. Can be ported to Windows by changing the /var/www to C:/hgolf and changing main= to main=withSocketsDo$, since the sockets library on Windows requires explicit initialization.

Ungolfed version:

import Data.List
import Network
import System.Directory
import System.IO

root = "/var/www"

main = do
    s <- listenOn (PortNumber 36895)
    loop s

loop s = (next s `catch` \e -> return ()) >> loop s

next s = do
    (h, _, _) <- accept s
    hGetContents h >>= serve >>= hPutStr h
    hClose h

serve req =
    case words req of
        ("GET" : path : _) -> deliver (root ++ path)
        _ -> complain "405 Not Supported"

deliver path = do
    isFile <- doesFileExist path
    if isFile
        then transfer path `catch` (\e -> complain "500 Server Error")
        else do isDir <- doesDirectoryExist path
                if isDir
                    then return $ response "200 OK" [] ""
                    else complain "404 File Not Found"

transfer path = do
   body <- hGetContents =<< openBinaryFile path ReadMode
   return $ response "200 OK" ["Content-Type: " ++ guessType path] body

response status headers body =
  concat [unlines $ ("HTTP/1.0 " ++ status) : headers, "\n", body]

complain status = return $ response status [] status

guessType path
    | ".html" `isSuffixOf` path = "text/html"
    | ".txt"  `isSuffixOf` path = "text/plain"
    | otherwise                 = "application/octet-stream"

hammar

Posted 2011-11-26T13:15:14.170

Reputation: 4 011

12

Python 2, 525 510 493 (bending the rules 483) chars

from socket import*
from os.path import*
a=socket(2,1)
a.bind(("",80))
a.listen(5)
while 1:
 c,_=a.accept();i=c.recv(512).split("\n")[0].split();z=i[1][1:];m=i[2]+(i[0]!="GET"and"405 Not Supported\n\n"*2or exists(z)-1and"404 File Not Found\n\n"*2or"200 OK\n")
 if(len(m)>16)+isdir(z)<1:
    try:f=open(z,"rb");m+="Content-Type: "+{".txt":"text/plain","html":"text/html"}.get(z[-4:],"application/octet-stream")+"\n\n"+f.read()
    except:m=i[2]+"500 Server Error\n\n"*2
 c.send(m);c.close()

I stated that I only need 483 chars if I bend the rules because the last ;c.close() could be omitted. This is because the moment the next client gets accepted, the socket gets closed anyway. This will of course increase waiting time somewhat (Firefox will only display the page when the next client connects, Chrome will display it before, but will continue to load), but the rules do not require me to respond to the request immediately, only to do so at some point.

I am not certain whether this will work on Unixes because I used send instead of sendall and send does not guarantee to actually send everything it is fed. It does work on Windows.

The screenshot that doesn't proof anything because there is no way to tell whether I used Apache or my own server to create it

cemper93

Posted 2011-11-26T13:15:14.170

Reputation: 670

1You are also bending the rules by serving the current directory instead of C:\hgolf (8 characters) on port 80 instead of 36895 (3 characters). – manatwork – 2013-03-06T17:10:08.327

7

Groovy, 507 500 489 485

for(ServerSocket s=new ServerSocket(36895);;){t=s.accept()
o=t.outputStream
o<<"HTTP/1.1 "
e='\r\n'*2
d={o<<"$it$e$it"}
p=t.inputStream.newReader().readLine().split()
if(p[0]!='GET')d'405 Not Supported'else{f=new File('/var/www/',p[1])
if(!f.exists())d'404 File Not Found'else if(f.isFile()){x=f.name=~/\.(.*)/
o<<"200 OK\r\nContent-Type: ${[html:'text/html',txt:'text/plain'][!x?:x[0][1]]?:'application/octet-stream'}$e"
try{o.bytes=f.bytes}catch(t){d"500 Server Error"}}}
o.close()}

enter image description here

Added the image download to show binary files are working properly - had a few issues with them to begin with.

Can anyone suggest a shorter way to read the input?

Armand

Posted 2011-11-26T13:15:14.170

Reputation: 499

5

Erlang (escript) 575

Very dirty Erlang escript. There have to be one blank line at beginning of file to work well:

$ cat hgolf.erl

main(_)->{ok,L}=gen_tcp:listen(36895,[]),s(L).
s(L)->{ok,S}=gen_tcp:accept(L),receive{tcp,S,"GET "++R}->[F|_]=string:tokens("/var/www"++R," "),case case file:read_file_info(F)of{ok,{_,_,regular,read,_,_,_,_,_,_,_,_,_,_}}->a;{ok,_}->"500 Server Error";_->"404 File Not Found"end of a->h(S,"200 OK\r\nContent-Type: "++case lists:reverse(F)of"lmth."++_->"text/html";"txt."++_->"text/plain";_->"application/octet-stream"end,[]),file:sendfile(F,S);E->h(S,E,E)end;_->E="405 Not Supported",h(S,E,E)end,gen_tcp:close(S),s(L).
h(S,H,B)->gen_tcp:send(S,["HTTP/1.1 ",H,"\r\n\r\n",B]).

How to run

$ escript hgolf.erl

Screenshot

Edit:

I have squeezed out 20 chars. case is surprisingly shorter than function with even one argument and three clauses.

Hynek -Pichi- Vychodil

Posted 2011-11-26T13:15:14.170

Reputation: 350

btw you could post that thing in this question, too (http://codegolf.stackexchange.com/questions/31647/)

– masterX244 – 2014-06-22T17:27:20.973

4

VB.NET, 7203

Picture of server running

You can define any port and any base directory using --port and --base, respectively.

No, this isn't really a golfing solution. But being VB.NET, there's really no point anyway. On the plus side, this one has lots more features.

Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Text.RegularExpressions

Public Module Server
    Private Const READ_BUFFER_SIZE As Integer = 1024

#Region "Content-Type Identification"
    Private ReadOnly ContentTypes As New Dictionary(Of String, String) From {
            {".htm", "text/html"},
            {".html", "text/html"},
            {".js", "text/javascript"},
            {".css", "text/css"},
            {".png", "image/png"},
            {".jpg", "image/jpeg"},
            {".jpeg", "image/jpeg"},
            {".gif", "image/gif"}
        } 'Feel free to add more.

    ''' <summary>
    ''' Retrieves the Content-Type of the specified file.
    ''' </summary>
    ''' <param name="filepath">The file for which to retrieve the Content-Type.</param>
    Private Function GetContentType(ByVal filepath As String) As String
        Dim ext As String = IO.Path.GetExtension(filepath)

        If ContentTypes.ContainsKey(ext) Then _
            Return ContentTypes(ext)

        Return "text/plain"
    End Function
#End Region

#Region "Server Main()"
    Public Sub Main(ByVal args() As String)
        Try
            'Get a dictionary of options passed:
            Dim options As New Dictionary(Of String, String) From {
                {"--port", "8080"},
                {"--address", "127.0.0.1"},
                {"--base", String.Empty}
            }

            For i As Integer = 0 To args.Length - 2
                If args(i).StartsWith("-") AndAlso options.ContainsKey(args(i)) Then _
                    options(args(i)) = args(i + 1)
            Next

            'Get the base directory:
            Dim basedir As String = Path.Combine(My.Computer.FileSystem.CurrentDirectory, options("--base"))

            'Start listening:
            Dim s As New TcpListener(IPAddress.Parse(options("--address")), Integer.Parse(options("--port"))) 'Can be changed.
            Dim client As TcpClient

            s.Start()

            Do
                'Wait for the next TCP client, and accept the connection:
                client = s.AcceptTcpClient()

                'Read the data being sent to the server:
                Dim ns As NetworkStream = client.GetStream()
                Dim sendingData As New Text.StringBuilder()
                Dim rdata(READ_BUFFER_SIZE - 1) As Byte
                Dim read As Integer

                Do
                    read = ns.Read(rdata, 0, READ_BUFFER_SIZE)
                    sendingData.Append(Encoding.UTF8.GetString(rdata, 0, read))
                Loop While read = READ_BUFFER_SIZE

                'Get the method and requested file:
#If Not Debug Then
                Try
#End If
                If sendingData.Length > 0 Then
                    Dim data As String = sendingData.ToString()

                    Dim headers() As String = data.Split({ControlChars.Cr, ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)
                    Dim basicRequestInfo() As String = headers(0).Split(" "c)
                    Dim method As String = basicRequestInfo(0)
                    Dim filepath As String = basicRequestInfo(1).Substring(1)
                    Dim actualFilepath As String = Path.Combine(basedir, Uri.UnescapeDataString(Regex.Replace(filepath, "\?.*$", "")).TrimStart("/"c).Replace("/"c, "\"c))
                    Dim httpVersion As String = basicRequestInfo(2)

                    'Set up the response:
                    Dim responseHeaders As New Dictionary(Of String, String)

                    Dim statusCode As String = "200"
                    Dim statusReason As String = "OK"
                    Dim responseContent() As Byte = {}

                    'Check the HTTP version - we only support HTTP/1.0 and HTTP/1.1:
                    If httpVersion <> "HTTP/1.0" AndAlso httpVersion <> "HTTP/1.1" Then
                        statusCode = "505"
                        statusReason = "HTTP Version Not Supported"
                        responseContent = Encoding.UTF8.GetBytes("505 HTTP Version Not Supported")
                    Else

                        'Attempt to check if the requested path is a directory; if so, we'll add index.html to it:
                        Try
                            If filepath = String.Empty OrElse filepath = "/" Then
                                actualFilepath = Path.Combine(basedir, "index.html")
                                filepath = "/"
                            ElseIf Directory.Exists(actualFilepath) Then
                                actualFilepath = Path.Combine(actualFilepath, "index.html")
                            End If
                        Catch
                            'Ignore the error; it will appear once again when we try to read the file.
                        End Try

                        'Check the method - we only support GET and HEAD:
                        If method = "GET" Then
                            'Make sure nobody's trying to hack the system by requesting ../whatever or an absolute path:
                            If filepath.Contains("..") Then
                                statusCode = "403"
                                statusReason = "Forbidden"
                                responseContent = Encoding.UTF8.GetBytes("403 Forbidden")
                                Console.WriteLine("Access to {0} was forbidden.", filepath)
                            ElseIf Not File.Exists(actualFilepath) Then
                                statusCode = "404"
                                statusReason = "Not Found"
                                responseContent = Encoding.UTF8.GetBytes("404 Not Found")
                                Console.WriteLine("A request for file {0} resulted in a 404 Not Found. The actual path was {1}.", filepath, actualFilepath)
                            Else
                                Try
                                    'Read the requested file:
                                    responseContent = File.ReadAllBytes(actualFilepath)

                                    'Get the requested file's length:
                                    responseHeaders.Add("Content-Length", responseContent.Length.ToString())

                                    'And get its content type too:
                                    responseHeaders.Add("Content-Type", GetContentType(actualFilepath))
                                Catch
                                    'Couldn't get the file's information - assume forbidden.
                                    statusCode = "403"
                                    statusReason = "Forbidden"
                                    responseContent = Encoding.UTF8.GetBytes("403 Forbidden")
                                End Try
                            End If
                        ElseIf method = "HEAD" Then
                            'Make sure nobody's trying to hack the system by requesting ../whatever or an absolute path:
                            If filepath.Contains("..") Then
                                statusCode = "403"
                                statusReason = "Forbidden"
                                responseContent = Encoding.UTF8.GetBytes("403 Forbidden")
                                Console.WriteLine("Access to {0} was forbidden.", filepath)
                            ElseIf Not File.Exists(actualFilepath) Then
                                statusCode = "404"
                                statusReason = "Not Found"
                                responseContent = Encoding.UTF8.GetBytes("404 Not Found")
                                Console.WriteLine("A request for file {0} resulted in a 404 Not Found.", filepath)
                            Else
                                Try
                                    'Get the requested file's length:
                                    responseHeaders.Add("Content-Length", New FileInfo(actualFilepath).Length.ToString())

                                    'And get its content type too:
                                    responseHeaders.Add("Content-Type", GetContentType(actualFilepath))
                                Catch
                                    'Couldn't get the file's information - assume forbidden.
                                    statusCode = "403"
                                    statusReason = "Forbidden"
                                    responseContent = Encoding.UTF8.GetBytes("403 Forbidden")
                                End Try
                            End If
                        Else
                            'Unknown method:
                            statusCode = "405"
                            statusReason = "Method Not Allowed"
                        End If

                        'Prepare the response:
                        Dim response As New List(Of Byte)

                        'Prepare the response's HTTP version and status:
                        response.AddRange(Encoding.UTF8.GetBytes("HTTP/1.1 " & statusCode & statusReason & ControlChars.CrLf))

                        'Prepare the response's headers:
                        Dim combinedResponseHeaders As New List(Of String)
                        For Each header As KeyValuePair(Of String, String) In responseHeaders
                            combinedResponseHeaders.Add(header.Key & ": " & header.Value)
                        Next
                        response.AddRange(Encoding.UTF8.GetBytes(String.Join(ControlChars.CrLf, combinedResponseHeaders.ToArray())))

                        'Prepare the response's content:
                        response.Add(13)
                        response.Add(10)
                        response.Add(13)
                        response.Add(10)
                        response.AddRange(responseContent)

                        'Finally, write the response:
                        ns.Write(response.ToArray(), 0, response.Count)
                    End If
                End If
#If Not Debug Then
                Catch ex As Exception
                    Console.WriteLine("Serious error while processing request:")
                    Console.WriteLine(ex.ToString())
                    Dim errorResponse() As Byte = Encoding.UTF8.GetBytes("HTTP/1.1 500 Internal Server Error" & ControlChars.CrLf & ControlChars.CrLf & "500 Internal Server Error")
                    ns.Write(errorResponse, 0, errorResponse.Length)
                End Try
#End If

                'And at last, close the connection:
                client.Close()
            Loop
        Catch ex As SocketException
            Console.WriteLine("SocketException occurred. Is the socket already in use?")
            Console.ReadKey(True)
        End Try
    End Sub
#End Region

End Module

I even decided to put it on GitHub :) https://github.com/minitech/DevServ

Ry-

Posted 2011-11-26T13:15:14.170

Reputation: 5 283

@minitech I've golfed your answer a little and posted it (https://codegolf.stackexchange.com/a/21757/15022), and if you want, copy the code from that and I'll delete my answer.

– Toothbrush – 2014-02-22T14:01:18.363

@minitech On line 46 of the code on Github, you have For i As Integer = 0 To args.Length - 2. If you add Step 2 to the end of that line, you increase the counter by two instead of 1.

– Toothbrush – 2014-06-29T14:03:37.913

2Haha, yeah, VB.NET isn't exactly the golfer's language of choice. Still nice though. – Polynomial – 2011-11-30T06:48:20.257

VB.NET can be golfed; there are quite a few tricks you can use. – Joey – 2011-12-05T19:28:06.523

@Joey: Yes, but there's no way I can compete with the current answers. I would take the comments out first :) – Ry- – 2011-12-05T20:39:59.417

4

C# (869)

It worked

using E=System.Text.Encoding;using System.IO;class C{static void Main(){var l=new System.Net.Sockets.TcpListener(new System.Net.IPEndPoint(16777343,36895));l.Start();while(0<1){using(var c=l.AcceptTcpClient()){try{string v="200 OK",r="",t="text/plain",p;var s=c.GetStream();var b=new byte[c.ReceiveBufferSize];s.Read(b,0,b.Length);var h=E.UTF8.GetString(b).Split();b=null;try{if(h[0]=="GET"){p="/var/www"+h[1];if(File.Exists(p)){b=File.ReadAllBytes(p);t=p.EndsWith(".txt")?t:p.EndsWith(".html")?"text/html":"application/octet-stream";}else if(!Directory.Exists(p)){v=r="404 Not Found";}}else{v=r="405 Not Supported";}}catch(IOException){v=r="500 Server Error";}b=b??E.UTF8.GetBytes(r);var m=E.UTF8.GetBytes("HTTP/1.1 "+v+"\r\nContent-Type:"+t+";charset=utf-8\r\nContent-Length:"+b.Length+"\r\n\r\n");s.Write(m,0,m.Length);s.Write(b,0,b.Length);}catch(IOException){}}}}}

Ungolfed

using System.Text;
using System.IO;

class C {
    static void Main() {
        var listener = new System.Net.Sockets.TcpListener(new System.Net.IPEndPoint(16777343,36895));
        listener.Start();

        while (true){
            using (var client = listener.AcceptTcpClient()) {
                try {
                    string responseCode = "200 OK", responseBody = "", contentType = "text/plain", path;
                    var stream = client.GetStream();

                    var bytes = new byte[client.ReceiveBufferSize];
                    stream.Read(bytes,0,bytes.Length);

                    var requestHeaders = Encoding.UTF8.GetString(bytes).Split();
                    bytes = null;

                    try{
                        if(requestHeaders[0] == "GET"){
                            path = "/var/www" + requestHeaders[1];

                            if (File.Exists(path)) {
                                bytes = File.ReadAllBytes(path);
                                contentType = path.EndsWith(".txt") ? contentType : path.EndsWith(".html") ? "text/html" : "application/octet-stream";
                            } else if (!Directory.Exists(path)){
                                responseCode = responseBody = "404 Not Found";
                            }
                        } else {
                            responseCode = responseBody = "405 Not Supported";
                        }
                    } catch(IOException) {
                        responseCode = responseBody = "500 Server Error";
                    }
                    bytes = bytes ?? Encoding.UTF8.GetBytes(responseBody);

                    var responseHeader=Encoding.UTF8.GetBytes("HTTP/1.1 " + responseCode + "\r\nContent-Type:" + contentType + ";charset=utf-8\r\nContent-Length:" + bytes.Length + "\r\n\r\n");
                    stream.Write(responseHeader, 0, responseHeader.Length);
                    stream.Write(bytes, 0, bytes.Length);
                } catch(IOException) {
                    // If a client disconnects in the middle of a request (e.g. by refreshing the browser) an IOException is thrown.
                }
            }
        }
    }
}

ICR

Posted 2011-11-26T13:15:14.170

Reputation: 211

3

Node.js - 636

Only tested on Linux, won't work on Windows.

a=require
b=a('fs')
function c(){g+=o+h+i+j+f+f+o+f}
a('net').createServer(function(d){d.on('data',function(e){f='\r\n'
g='HTTP/1.1 '
h=f+'Content-Type: '
i='text/'
j='plain'
if(k=/^GET (\S+)/.exec((e+'').split(f)[0])){k=k[1]
l=k.slice(k.lastIndexOf('.')+1)
m='www'+k
if(b.existsSync(m)){if(b.lstatSync(m).isDirectory()){g+='200 OK'+h+i+j+f}else{try{n=b.readFileSync(m)
g+='200 OK'+h
if(l=='txt')g+=i+j
else if(l=='html')g+=i+l
else g+='application/octet-stream'
g+=f+f+n}catch(_){o='500 Server Error'
c()}}}else{o='404 File Not Found'
c()}}else{o='405 Not Supported'
c()}
d.write(g)
d.end()})
d.on('error',function(){})}).listen(36895)

Screenshot

nyuszika7h

Posted 2011-11-26T13:15:14.170

Reputation: 1 624

2

Scala, 653 characters

import java.net._
import java.io._
object I extends App{val l=new ServerSocket(36895)
while(true){var (s,e,c,b)=(l.accept,"200 OK","text/html","")
var h=io.Source.fromInputStream(s.getInputStream).getLines.next.split(" ")
if(h(0)!="GET"){e="405 Not Supported"
b=e}else{var f=new File("/var/www"+h(1))
if(!f.isDirectory){if(f.exists){var q=h(1).split("\\.").last
if(q=="txt")c="text/plain"else if(q!="html")c="application/octet-stream"
try b=io.Source.fromFile(f).mkString catch{case _=>e="500 Server Error";b=e}}else{e="404 File Not Found"
b=e}}}
var o=new PrintWriter(s.getOutputStream)
o.print("HTTP/1.1 "+e+"\nContent-Encoding:"+c+"\n\n"+b)
o.close}}

A screenshot of it running on my MacBook:

Screenshot

Not great, but I'll have a go at squashing it a bit when I've got some time later.

Gareth

Posted 2011-11-26T13:15:14.170

Reputation: 11 678

Got a screenshot of it in action? – Polynomial – 2011-11-27T14:32:01.937

@Polynomial Yeah sorry, was in a bit of a rush to go somewhere earlier. – Gareth – 2011-11-27T16:28:43.533

2

Python 3 - 655

from socket import*
import re
import threading
def x(c):
    u=str(c.recv(1024))
    if not'GET'in u:conn.sendall(t("HTTP/1.1 405 Not Supported"))
    u=re.search('GET [^\s]+ HTTP/1.1',u).group(0).split(" ")[1];u="/index.html" if u == "/" else u;e=u.split(".")[1]
    try:c.sendall(t("HTTP/1.1 200 OK\nContent-Type: "+({'txt':'text/plain','html':'text/html'}[e]if e in'txthtml'else'application/octet-stream')+"\n\n")+open("."+u,'rb').read())
    except:c.sendall(t("HTTP/1.1 404 File Not Found\n\n404 File Not Found"))
t=lambda s: bytes(s,'utf8')
s=socket(AF_INET,SOCK_STREAM)
s.bind(('',36895))
s.listen(10)
while 1:threading.Thread(target=x,args=[s.accept()[0]]).run()

enter image description here

Tuomas Laakkonen

Posted 2011-11-26T13:15:14.170

Reputation: 341

afaik you can use 1-space indentation (rather than 4) to save 15 chars – James Vickery – 2019-03-08T18:07:06.097

1

VB.Net (3504 characters):

Imports System.IO:Imports System.Net:Imports System.Net.Sockets:Imports System.Text:Imports System.Text.RegularExpressions:Module x:Dim s=1024,ct As New Dictionary(Of String,String)From{{".htm","text/html"},{".html","text/html"},{".js","text/javascript"},{".css","text/css"},{".png","image/png"},{".jpg","image/jpeg"},{".jpeg","image/jpeg"},{".gif","image/gif"}}:Function b$(f$):Dim ext$=Path.GetExtension(f$):Return If(ct.ContainsKey(ext$),ct(ext$),"text/plain"):End Function:Sub Main(a$()):Try:Dim z As New Dictionary(Of String,String)From{{"--port","8080"},{"--address","127.0.0.1"},{"--base",""}}:For i As Integer=0 To a.Length-2:If a$(i).StartsWith("-")AndAlso z.ContainsKey(a$(i))Then:z(a$(i))=a$(i+1):Next:Dim b$=Path.Combine(My.Computer.FileSystem.CurrentDirectory,z("--base")),s As New TcpListener(IPAddress.Parse(z("--address")),Integer.Parse(z("--port"))),c As TcpServer:s.Start():Do:c=s.AcceptTcpServer():Dim ns As NetworkStream=c.GetStream():Dim sd As New Text.StringBuilder(),rd(s-1)As Byte,r As Integer:Do:r=ns.Read(rd,0,s):sd.Append(Encoding.UTF8.GetString(rd,0,r)):Loop While r=s:Try:If sd.Length>0 Then:Dim dd$=sd.ToString(),h$()=dd$.Split({ControlChars.Cr,ControlChars.Lf},StringSplitOptions.RemoveEmptyEntries),br$()=h$(0).Split(" "c),mt$=br$(0),f$=br$(1).Substring(1),af$=Path.Combine(b$,Uri.UnescapeDataString(Regex.Replace(f$,"\?.*$","")).TrimStart("/"c).Replace("/"c,"\"c)),hv$=br$(2),rh As New Dictionary(Of String,String),sc$="200",sr$="OK",rc()As Byte={}:If hv$<>"HTTP/1.0"AndAlso hv$<>"HTTP/1.1"Then:sc$="505":sr$="HTTP Version Not Supported":rc=Encoding.UTF8.GetBytes("505"&sr$):Else:Try:If f$=String.Empty OrElse f$="/"Then:af$=Path.Combine(b$,"index.html"):f$="/":ElseIf Directory.Exists(af$)Then:af$=Path.Combine(af$,"index.html"):End If:Catch:End Try:If mt$="GET"Then:If f$.Contains("..")Then:sc$="403":sr$="Forbidden":rc=Encoding.UTF8.GetBytes(sc$&" "&sr$):Console.WriteLine("{0} forbidden.",f$):ElseIf Not File.Exists(af$)Then:sc$="404":sr$="Not Found":rc=Encoding.UTF8.GetBytes(sc$&" "&sr$):Console.WriteLine("{0} resulted in 404 Not Found. Path {1}.",f$,af$):Else:Try:rc=File.ReadAllBytes(af$):rh.Add("Content-Length",rc.Length&""):rh.Add("Content-Type",b$(af$)):Catch:sc$="403":sr$="Forbidden":rc = Encoding.UTF8.GetBytes(sc$&" "&sr$):End Try:End If:ElseIf mt$="HEAD"Then:If f$.Contains("..")Then:sc$="403":sr$="Forbidden":rc=Encoding.UTF8.GetBytes(sc$&" "&sr$):Console.WriteLine("{0} forbidden.",f$):ElseIf Not File.Exists(af$)Then:sc$="404":sr$="Not Found":rc=Encoding.UTF8.GetBytes(sc$&" "&sr$):Console.WriteLine("404 Not Found: {0}",f$):Else:Try:rh.Add("Content-Length",New FileInfo(af$).Length&""):rh.Add("Content-Type",b$(af$)):Catch:sc$="403":sr$="Forbidden":rc = Encoding.UTF8.GetBytes(sc$&" "&sr$):End Try:End If:Else:sc$="405":sr$="Method Not Allowed":End If:Dim rr As New List(Of Byte):rr.AddRange(Encoding.UTF8.GetBytes("HTTP/1.1 "&sc$&sr$&ControlChars.CrLf)):Dim cr As New List(Of String):For Each h As KeyValuePair(Of String,String)In rh:cr.Add(h.Key&": "&h.Value):Next:rr.AddRange(Encoding.UTF8.GetBytes(String.Join(ControlChars.CrLf,cr.ToArray()))):rr.Add(13):rr.Add(10):rr.Add(13):rr.Add(10):rr.AddRange(rc):ns.Write(rr.ToArray(),0,rr.Count):End If:End If:Catch ex As Exception:Console.WriteLine("Error:"):Console.WriteLine(ex.ToString()):Dim e()As Byte=Encoding.UTF8.GetBytes("HTTP/1.1 500 Internal x Error"&ControlChars.CrLf &ControlChars.CrLf &"500 Internal x Error"):ns.Write(e,0,e.Length):End Try:c.Close():Loop:Catch:End Try:End Sub:End Module

Golfed from @minitech's answer.

Toothbrush

Posted 2011-11-26T13:15:14.170

Reputation: 3 197

1

Lua 5.1 in 435 434 bytes

s=require'socket'.bind('*',36895)while{}do
c=s:accept()u=c:receive'*l':match'GET (.*) HTTP'f,_,e=io.open('/var/www'..u,'rb')d=z
x=z if f then d,_,x=f:read'*a'end
h=u and(x==21 and''or(e==2 and'404 File Not Found'or d
and('200 OK\r\nContent-Type:'..(({txt='text/plain',html='text/html'})[u:match'%.(.-)$']or'application/octet-stream'))or'500 Server Error'))or'405 Not Supported'c:send('HTTP/1.1 '..h..'\r\n\r\n'..(d
or h))c:close()end

...and the proof...

thenumbernine

Posted 2011-11-26T13:15:14.170

Reputation: 341