- currently only responds to GET requests (no POST, so no forms)
- can send html or htm files as Content-Type: text/html, other file extensions are sent as text/plain.
- looks for index.html as default file in a directory
known issues:
- does not "keep-alive" connections, once data is sent from server, connection is closed
feel free to modify to suit your needs
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases. #SingleInstance force SetBatchLines -1 ; -------------------------- ; ----------SERVER---------- ; -------------------------- ; ------------------- ; START CONFIGURATION ; ------------------- ; Specify Your own Network's address and port. Network_Address = 0.0.0.0 Network_Port = 80 webRoot = C:\www ; -------------------- ; END OF CONFIGURATION ; -------------------- Gosub Connection_Init return Connection_Init: OnExit, ExitSub ; For connection cleanup purposes. socket := PrepareForIncomingConnection(Network_Address, Network_Port) if socket = -1 ; Connection failed (it already displayed the reason). ExitApp ; Find this script's main window: Process, Exist ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts). DetectHiddenWindows On ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel) DetectHiddenWindows Off ; When the OS notifies the script that there is incoming data waiting to be received, ; the following causes a function to be launched to read the data: NotificationMsg = 0x5555 ; An arbitrary message number, but should be greater than 0x1000. OnMessage(NotificationMsg, "ReceiveData") ; Set up the connection to notify this script via message whenever new data has arrived. ; This avoids the need to poll the connection and thus cuts down on resource usage. FD_READ = 1 ; Received when data is available to be read. FD_CLOSE = 32 ; Received when connection has been closed. FD_CONNECT = 20 ; Received when connection has been made. FD_ACCEPT = 8 ; Received when connection has been accepted. if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_ACCEPT|FD_READ|FD_CLOSE|FD_CONNECT) { MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") ExitApp } return PrepareForIncomingConnection(IPAddress, Port) ; This can connect to most types of TCP servers, not just Network. ; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success. { VarSetCapacity(wsaData, 32) ; The struct is only about 14 in size, so 32 is conservative. result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002) ; Since WSAStartup() will likely be the first Winsock function called by this script, ; check ErrorLevel to see if the OS has Winsock 2.0 available: if ErrorLevel { MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required. return -1 } if result ; Non-zero, which means it failed (most Winsock functions return 0 upon success). { MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } AF_INET = 2 SOCK_STREAM = 1 IPPROTO_TCP = 6 socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP) if socket = -1 { MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") return -1 } ; Prepare for connection: SizeOfSocketAddress = 16 VarSetCapacity(SocketAddress, SizeOfSocketAddress) InsertInteger(2, SocketAddress, 0, AF_INET) ; sin_family InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2) ; sin_port InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4) ; sin_addr.s_addr ; Bind to socket: if DllCall("Ws2_32\bind", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress) { MsgBox % "bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?" return -1 } if DllCall("Ws2_32\listen", "UInt", socket, "UInt", "SOMAXCONN") { MsgBox % "listen() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?" return -1 } return socket ; Indicate success by returning a valid socket ID rather than -1. } ReceiveData(wParam, lParam) ; By means of OnMessage(), this function has been set up to be called automatically whenever new data ; arrives on the connection. { critical ; So OnMessage doesn't interrupt (may cause conn_refused if there's lag in the rest of this function) global webRoot ;global ShowReceived socket := wParam ; accept requests that are in the pipeline of the socket ConnCheck := DllCall("Ws2_32\accept", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress) ; Ws2_22/accept returns the new Connection-Socket if a connection request was in the pipeline ; on failure it returns an negative value if ConnCheck > 1 ; new connection { ;TrayTip, , Connection Established, 5 ; Show connection accepted } else { ReceivedDataSize = 8192 ; Large in case a lot of data gets buffered due to delay in processing previous data. Loop ; This loop solves the issue of the notification message being discarded due to thread-already-running. { VarSetCapacity(ReceivedData, ReceivedDataSize, 0) ; 0 for last param terminates string for use with recv(). ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0) if ReceivedDataLength = 0 ; The connection was gracefully closed, { ;ShowReceived = ;TrayTip, , Connection Closed, 5 ; Show connection closed by client CloseSocket(socket) return 1 } if ReceivedDataLength = -1 { CloseSocket(socket) return 1 } ; Otherwise, process the data received. Loop, parse, ReceivedData, %A_Space% { ;ShowReceived = %ShowReceived%%A_LoopField% ;Tooltip % ShowReceived if A_Index = 2 { getFile = %A_LoopField% ; This is file requested break } } if ReceivedDataLength > 0 break } ;TrayTip, Data Received, %ShowReceived%, 5, 1 ; Show info about Received Data IfInString, ReceivedData, GET { oldfile = %webRoot%%getFile% StringReplace, file, oldfile, /, \, All FoundNoIndex := RegExMatch(file, "\\$") if FoundNoIndex > 0 file = %file%index.html FoundHTML := RegExMatch(file, ".html$") if FoundHTML > 0 ContentType = Content-Type: text/html else { FoundHTM := RegExMatch(file, ".htm$") if FoundHTML > 0 { ContentType = Content-Type: text/html } else ContentType = Content-Type: text/plain } FileRead, Contents, %file% if not ErrorLevel { SendText = HTTP/1.0 200 OK`n`r%ContentType%`n`r`n`r%Contents% } else { SendText = HTTP/1.0 404 Not Found`n`rContent-Type: text/plain`n`r`n`r404 Not Found } ; Clear ContentType ContentType = } else SendText = HTTP/1.0 501 Not Implemented`n`rContent-Type: text/plain`n`r`n`r501 Not Implemented SentOK := SendData(socket, SendText) ; Clear SendText SendText = CloseSocket(socket) ;Clear ShowReceived ;ShowReceived = } return 1 ; Tell the program that no further processing of this message is needed. } CloseSocket(wParam) { socket := wParam ;ShutStatus := DllCall("Ws2_32\shutdown", "UInt", socket, "Int", 0) ; Commented to prevent CONN_RESET errors ; The shutdown() last parameter is either ; SD_SEND: 0: Shutdown send operations. ; SD_RECEIVE: 1: Shutdown receive operations. ; SD_BOTH: 2: Shutdown both send and receive operations. CloseStatus := DllCall("Ws2_32\closesocket", "UInt", socket) } SendData(wParam, SendData) { socket := wParam sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendData, "Int", StrLen(SendData), "Int", 0) return sendret } InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4) ; The caller must ensure that pDest has sufficient capacity. To preserve any existing contents in pDest, ; only pSize number of bytes starting at pOffset are altered in it. { Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data. DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF) } ExitSub: ; This subroutine is called automatically when the script exits for any reason. ; MSDN: "Any sockets open when WSACleanup is called are reset and automatically ; deallocated as if closesocket was called." DllCall("Ws2_32\WSACleanup") ExitApp
edit:fixed for default index.html regex