Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

AHK Web Server [work in progress, but usable]


  • Please log in to reply
No replies to this topic
paftdunk
  • Members
  • 13 posts
  • Last active: Apr 13 2009 02:46 AM
  • Joined: 08 Apr 2009
building on my 404Server <!-- m -->http://www.autohotke...pic.php?t=42967<!-- m -->

- 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