Jump to content
jvanegmond

Powerful HTTP Server in Pure AutoIt

Recommended Posts

Thanks Manadar for your replay

well, here a very primitive attempt (primitive but at least it works) to achieve the purpose  (just a proof of concept).
in short:
added a new "Case" to the Switch $sFileType to catch .ha3 filetypes
added _parseSource() at bottom
added _ExecuteScript() at bottom

For who wants to try:
Save the modified server script and the 2 "web pages"  in the same directory,
run the server
open a web browser and enter as address  the IP of the machine where the server is running (a very basic page will open)
click on the only link and a second page with embedded Autoit code will be parsed on the server and opened on the client.

modified Basic_Server save it as Server.au3

#cs
    Resources:
    Internet Assigned Number Authority - all Content-Types: http://www.iana.org/assignments/media-types/
    World Wide Web Consortium - An overview of the HTTP protocol: http://www.w3.org/Protocols/

    Credits:
    Manadar for starting on the webserver.
    Alek for adding POST and some fixes
    Creator for providing the "application/octet-stream" MIME type.
#ce

; // OPTIONS HERE //
Local $sRootDir = @ScriptDir ; & "\www" ; The absolute path to the root directory of the server.
Local $sIP = @IPAddress1 ; ip address as defined by AutoIt
Local $iPort = 80 ; the listening port
Local $sServerAddress = "http://" & $sIP & ":" & $iPort & "/"
Local $iMaxUsers = 15 ; Maximum number of users who can simultaneously get/post
Local $sServerName = "ManadarX/1.1 (" & @OSVersion & ") AutoIt " & @AutoItVersion
; // END OF OPTIONS //

Local $aSocket[$iMaxUsers] ; Creates an array to store all the possible users
Local $sBuffer[$iMaxUsers] ; All these users have buffers when sending/receiving, so we need a place to store those

For $x = 0 To UBound($aSocket) - 1 ; Fills the entire socket array with -1 integers, so that the server knows they are empty.
    $aSocket[$x] = -1
Next

TCPStartup() ; AutoIt needs to initialize the TCP functions

$iMainSocket = TCPListen($sIP, $iPort) ;create main listening socket
If @error Then ; if you fail creating a socket, exit the application
    MsgBox(0x20, "AutoIt Webserver", "Unable to create a socket on port " & $iPort & ".") ; notifies the user that the HTTP server will not run
    Exit ; if your server is part of a GUI that has nothing to do with the server, you'll need to remove the Exit keyword and notify the user that the HTTP server will not work.
EndIf


ConsoleWrite("Server created on " & $sServerAddress & @CRLF) ; If you're in SciTE,

While 1
    $iNewSocket = TCPAccept($iMainSocket) ; Tries to accept incoming connections

    If $iNewSocket >= 0 Then ; Verifies that there actually is an incoming connection
        For $x = 0 To UBound($aSocket) - 1 ; Attempts to store the incoming connection
            If $aSocket[$x] = -1 Then
                $aSocket[$x] = $iNewSocket ;store the new socket
                ExitLoop
            EndIf
        Next
    EndIf

    For $x = 0 To UBound($aSocket) - 1 ; A big loop to receive data from everyone connected
        If $aSocket[$x] = -1 Then ContinueLoop ; if the socket is empty, it will continue to the next iteration, doing nothing
        $sNewData = TCPRecv($aSocket[$x], 1024) ; Receives a whole lot of data if possible
        If @error Then ; Client has disconnected
            $aSocket[$x] = -1 ; Socket is freed so that a new user may join
            ContinueLoop ; Go to the next iteration of the loop, not really needed but looks oh so good
        ElseIf $sNewData Then ; data received
            $sBuffer[$x] &= $sNewData ;store it in the buffer
            If StringInStr(StringStripCR($sBuffer[$x]), @LF & @LF) Then ; if the request has ended ..
                $sFirstLine = StringLeft($sBuffer[$x], StringInStr($sBuffer[$x], @LF)) ; helps to get the type of the request
                $sRequestType = StringLeft($sFirstLine, StringInStr($sFirstLine, " ") - 1) ; gets the type of the request
                If $sRequestType = "GET" Then ; user wants to download a file or whatever ..
                    $sRequest = StringTrimRight(StringTrimLeft($sFirstLine, 4), 11) ; let's see what file he actually wants
                    If StringInStr(StringReplace($sRequest, "\", "/"), "/.") Then ; Disallow any attempts to go back a folder
                        ;;~ _HTTP_SendError($aSocket[$x]) ; sends back an error
                        _HTTP_SendFileNotFoundError($aSocket[$x]) ; sends back an error
                    Else
                        If $sRequest = "/" Then ; user has requested the root
                            $sRequest = "/index.html" ; instead of root we'll give him the index page
                        EndIf
                        $sRequest = StringReplace($sRequest, "/", "\") ; convert HTTP slashes to windows slashes, not really required because windows accepts both
                        If FileExists($sRootDir & "\" & $sRequest) Then ; makes sure the file that the user wants exists
                            $sFileType = StringRight($sRequest, 4) ; determines the file type, so that we may choose what mine type to use
                            Switch $sFileType
                                ; -- just a pre alpha "proof of concept" -----------------------------------------
                                Case ".ha3" ; just for example extension .ha3 could be used -> (h)tml (a)utoit(3)
                                    $hFile = FileOpen($sRootDir & "\" & $sRequest);, 16) ; read file of web page
                                    $bFileData = FileRead($hFile)
                                    FileClose($hFile)
                                    ; now use _parseSource() to extract and execute embedded AutoIt code
                                    ; and then send result to client's browser
                                    _HTTP_SendData($aSocket[$x], _parseSource($bFileData), "text/html")
                                    ; ----------------------------------------------------------------------------
                                Case "html", ".htm" ; in case of normal HTML files
                                    _HTTP_SendFile($aSocket[$x], $sRootDir & $sRequest, "text/html")
                                Case ".css" ; in case of style sheets
                                    _HTTP_SendFile($aSocket[$x], $sRootDir & $sRequest, "text/css")
                                Case ".jpg", "jpeg" ; for common images
                                    _HTTP_SendFile($aSocket[$x], $sRootDir & $sRequest, "image/jpeg")
                                Case ".png" ; another common image format
                                    _HTTP_SendFile($aSocket[$x], $sRootDir & $sRequest, "image/png")
                                Case Else ; this is for .exe, .zip, or anything else that is not supported is downloaded to the client using a application/octet-stream
                                    _HTTP_SendFile($aSocket[$x], $sRootDir & $sRequest, "application/octet-stream")
                            EndSwitch
                        Else
                            _HTTP_SendFileNotFoundError($aSocket[$x]) ; File does not exist, so we'll send back an error..
                        EndIf
                    EndIf
                EndIf

                $sBuffer[$x] = "" ; clears the buffer because we just used to buffer and did some actions based on them
                $aSocket[$x] = -1 ; the socket is automatically closed so we reset the socket so that we may accept new clients

            EndIf
        EndIf
    Next

    Sleep(10)
WEnd

Func _HTTP_ConvertString(ByRef $sInput) ; converts any characters like %20 into space 8)
    $sInput = StringReplace($sInput, '+', ' ')
    StringReplace($sInput, '%', '')
    For $t = 0 To @extended
        $Find_Char = StringLeft(StringTrimLeft($sInput, StringInStr($sInput, '%')), 2)
        $sInput = StringReplace($sInput, '%' & $Find_Char, Chr(Dec($Find_Char)))
    Next
EndFunc   ;==>_HTTP_ConvertString

Func _HTTP_SendHTML($hSocket, $sHTML, $sReply = "200 OK") ; sends HTML data on X socket
    _HTTP_SendData($hSocket, Binary($sHTML), "text/html", $sReply)
EndFunc   ;==>_HTTP_SendHTML

Func _HTTP_SendFile($hSocket, $sFileLoc, $sMimeType, $sReply = "200 OK") ; Sends a file back to the client on X socket, with X mime-type
    Local $hFile, $sImgBuffer, $sPacket, $a

    ConsoleWrite("Sending " & $sFileLoc & @CRLF)

    $hFile = FileOpen($sFileLoc, 16)
    $bFileData = FileRead($hFile)
    FileClose($hFile)

    _HTTP_SendData($hSocket, $bFileData, $sMimeType, $sReply)
EndFunc   ;==>_HTTP_SendFile

Func _HTTP_SendData($hSocket, $bData, $sMimeType, $sReply = "200 OK")
    $sPacket = Binary("HTTP/1.1 " & $sReply & @CRLF & _
            "Server: " & $sServerName & @CRLF & _
            "Connection: close" & @CRLF & _
            "Content-Lenght: " & BinaryLen($bData) & @CRLF & _
            "Content-Type: " & $sMimeType & @CRLF & _
            @CRLF)
    TCPSend($hSocket, $sPacket) ; Send start of packet

    While BinaryLen($bData) ; Send data in chunks (most code by Larry)
        $a = TCPSend($hSocket, $bData) ; TCPSend returns the number of bytes sent
        $bData = BinaryMid($bData, $a + 1, BinaryLen($bData) - $a)
    WEnd

    $sPacket = Binary(@CRLF & @CRLF) ; Finish the packet
    TCPSend($hSocket, $sPacket)

    TCPCloseSocket($hSocket)
EndFunc   ;==>_HTTP_SendData

Func _HTTP_SendFileNotFoundError($hSocket) ; Sends back a basic 404 error
    Local $s404Loc = $sRootDir & "\404.html"
    If (FileExists($s404Loc)) Then
        _HTTP_SendFile($hSocket, $s404Loc, "text/html")
    Else
        _HTTP_SendHTML($hSocket, "404 Error: " & @CRLF & @CRLF & "The file you requested could not be found.")
    EndIf
EndFunc   ;==>_HTTP_SendFileNotFoundError

; --- new pre alpha test functions ---------------
;
; following function grabbed from AuCGI.au3
; from here: http://www.autoitscript.com/forum/topic/111133-autoit-cgi-handler-aucgi/
; by Erik Pilsits (wraithdu), and Josh Rowe (JRowe)
; original author Matt Roth (theguy0000) <theguy0000@gmail.com>
; (slightly modified by me)
;
; parse source for <?au3 ?> code
Func _parseSource($source)
    Local $idx = 1, $idx2, $lastidx = 1, $parsed = "", $chunk = ""
    Do
        ; get first code snippet
        $idx = StringInStr($source, "<?au3", 0, 1, $idx) ; position of "<?au3" in $idx
        If $idx Then
            If $idx > $lastidx Then
                ; we have html
                $chunk = StringMid($source, $lastidx, $idx - $lastidx) ; get it (get html)
                $parsed &= $chunk ;~ _splitHTML($chunk) ; write it to $parsed
            EndIf
            $idx += 5 ; start of code
            ; get end of code tag
            $idx2 = StringInStr($source, "?>", 0, 1, $idx)
            If $idx2 Then
                ; found end of code --------------------------------------------------------------+
                $chunk = StringMid($source, $idx, $idx2 - $idx) ; get it (portion of AutoIt code) |
                ; --- execute autoit code and get back result ---                                 |
                $chunk = _ExecuteScript($chunk) ;                                                 |
                $parsed &= $chunk & @CRLF ;                                                       |
                $lastidx = $idx2 + 2 ; new $lastidx value, set to position after end-code tag     |
                $idx = $lastidx ; next search start location -------------------------------------+
            Else
                ; parse error, get out
                ConsoleWrite("Error parsing source.")
            EndIf
        Else
            ; no code sections or last section of html
            $chunk = StringMid($source, $lastidx) ; get it (html)
            If $chunk Then $parsed &= $chunk ;~ _splitHTML($chunk) ; check we actually have something this time, write it
        EndIf
    Until Not $idx
    Return $parsed
EndFunc   ;==>_parseSource

; following function is by trancexx (slightly modified by me)
; from here: http://www.autoitscript.com/forum/topic/82461-how-to-get-access-for-a3x-encoded-files/?p=590906
Func _ExecuteScript($code)
    Local $return
    Local $TypeLib = ObjCreate("Scriptlet.TypeLib")
    Local $tmp = @TempDir & "\~" & $TypeLib.Guid & "tempexec.tmp"
    Local $hwnd = FileOpen($tmp, 26)
    FileWrite($hwnd, $code)
    FileClose($hwnd)
    $pid = Run('"' & @AutoItExe & '" /AutoIt3ExecuteScript "' & $tmp & '"', "", "", 0x2) ; 0x2 ($STDOUT_CHILD)
    Do
        $return = StdoutRead($pid)
        Sleep(50)
    Until $return <> ""
    FileDelete($tmp)
    Return $return
    ;
EndFunc   ;==>_ExecuteScript

First html page, save it in the same directory of the server as index.html

<!DOCTYPE html>
<html>
<body>

<a href="index.ha3">Try a web page with AutoiT code embedded!</a>

</body>
</html>

Second html page with AutoIt code embedded. Save it in the same directory of the server as index.ha3

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<?au3
    $echo = "<br></br>"
    $echo &= "Hello World - from Manadar's web Server"
    $echo &= "<br></br>"
    $echo &= "Sever hosted on " & @ComputerName
    $echo &= "<br></br>"
    $echo &= "Server architecture is:"  & "<br></br>"
    $echo &= "CPUArch: " & @CPUArch & "<br></br>"
    $echo &= "OSArch: " & @OSArch & "<br></br>"
    $echo &= "OSType: " & @OSType & "<br></br>"
    $echo &= "OSVersion: " & @OSVersion & "<br></br>"
    ConsoleWrite($echo)
?>
 <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
 <title>Test</title>
</head>
<body>
<?au3
    $echo = "<br></br>"
    $echo &= "Server date is " & @MON & "/" & @MDAY & "/" & @YEAR
    $echo &= "<br></br>"
    $echo &= "Server Time is " & @HOUR & ":" & @MIN & ":" & @SEC
    ConsoleWrite($echo)
?>
</body>
</html>
Edited by Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Share this post


Link to post
Share on other sites

where can i add in the code to know when a IP has stopped requesting?

i have tried adding like this and it is not working, it does get the right IP tho

  $iNewSocket = TCPAccept($iMainSocket) ; Tries to accept incoming connections

    If $iNewSocket >= 0 Then ; Verifies that there actually is an incoming connection

   $newip = SocketToIP($iNewSocket)
        ConsoleWrite ($newip & @CRLF)

        For $x = 0 to UBound($aSocket)-1 ; Attempts to store the incoming connection
            If $aSocket[$x] = -1 Then
                $aSocket[$x] = $iNewSocket ;store the new socket
                ExitLoop


            EndIf
        Next
    EndIf


    For $x = 0 to UBound($aSocket)-1 ; A big loop to receive data from everyone connected
        If $aSocket[$x] = -1 Then ContinueLoop ; if the socket is empty, it will continue to the next iteration, doing nothing
        $sNewData = TCPRecv($aSocket[$x],1024) ; Receives a whole lot of data if possible

        If @error Then ; Client has disconnected
  ConsoleWrite ($newip & " has stopped recieveing " & @CRLF)

I also want to take that IP and save to a txt file but do not want to have it write over and over...  like it does in the console

i plan on getting the IP and time was connected and IP and Time disconnected

Share this post


Link to post
Share on other sites

I'm not sure I understand but let's give it a shot.

Maybe you just are confused because you are coming from a modern HTTP/1.1 background. This server avoids some implementation problems by closing the underlying TCP connection after every HTTP request/response pair. That's a simplification of the HTTP/1.1 protocol which you may understand from the way that most servers implement it, but it's definitely not breaking the HTTP spec so all browsers to my knowledge do correctly handle it. The downside is mainly that it's not as fast because of the additional roundtrip when establishing a new TCP connection.

So you're capturing the @error on TCPRecv but that happens after every request and not "when the client has stopped sending requests" which I understand is what you are asking about.

This introduces an interesting programming problem: You can have code execute when something happens, but there is no similar paradigm to execute code when something is not happening. The solution to this is generally to introduce another event, like a timer, executing code which contains a check on the time of the last actual event. If that is greater than a set amount, do some error handling. In AutoIt, there is the AdlibRegister function to create such a timer event.

Share this post


Link to post
Share on other sites

U  basically need to watch the _HTTP_SendFile function and if it hasnt been called in like 30 seconds i know it has stopped.

any idea how to implement that since AdlibRegister is for calling a function and the http send file is not really something you would call that way 

any help would be very appreciated! thanks

Share this post


Link to post
Share on other sites

I ran into a strange problem with the basic example. It doesn't work most of the time. I set the server up on a windows dedicated server (static ip .. blah). When I try to access the server using any browser outside the datacenter, It just loads indefinitely, and eventually times out. 

Using an AutoIt script on any PC outside the DC and InetRead, the valid result is returned every time. Also, some Web-based proxies do load the page (others don't, most fail with a libcurl timeout).

I suspect there is a significant difference on the requesting site when using InetRead or a Web Browser.

Recap: InetRead works everytime, everywhere. Browsing the page using Chrome, FF, etc. doesn't, except for some Webproxies.


I will answer every single PM, and you are free to ask anything anytime.

Share this post


Link to post
Share on other sites

Inetread will only read the source of the requested html page without trying to interpret it and performing any download of images, or execution on client side scripting.  

Jos


SciTE4AutoIt3 Full installer Download page   - Beta files       Read before posting     How to post scriptsource   Forum etiquette  Forum Rules 
 
Live for the present,
Dream of the future,
Learn from the past.
  :)

Share this post


Link to post
Share on other sites

I solved it, my local network policy blocked the requests :) 

 

I fixed a Bug btw: If there is a packet flood from a client who's trying to take you down, the send function will glitch out and loop indefinitely. To prevent this, we have to check that TCPSend actually sent any bytes, if not cut off the client:

 

Func _HTTP_SendData($hSocket, $bData, $sMimeType, $sReply = "200 OK")
    $sPacket = Binary("HTTP/1.1 " & $sReply & @CRLF & _
            "Server: " & $sServerName & @CRLF & _
            "Connection: close" & @CRLF & _
            "Content-Lenght: " & BinaryLen($bData) & @CRLF & _
            "Content-Type: " & $sMimeType & @CRLF & _
            @CRLF)
    TCPSend($hSocket, $sPacket) ; Send start of packet
    ConsoleWrite("# [HTML] Sent header." & @LF)

    Local $iLastData = -1 ; buffer to check packet flood

    While BinaryLen($bData) ; Send data in chunks (most code by Larry)
        $a = TCPSend($hSocket, $bData) ; TCPSend returns the number of bytes sent
        $bData = BinaryMid($bData, $a + 1, BinaryLen($bData) - $a)
        If BinaryLen($bData) = $iLastData And $iLastData >= 0 Then ExitLoop
        $iLastData = BinaryLen($bData)
    WEnd

    If Not $iLastData Then
        $sPacket = Binary(@CRLF & @CRLF) ; Finish the packet
        TCPSend($hSocket, $sPacket)
        ConsoleWrite("# [HTML] Sent everything" & @LF)
    EndIf

    TCPCloseSocket($hSocket)
    ConsoleWrite("# [HTML] Closed connection." & @LF)
EndFunc   ;==>_HTTP_SendData

 


I will answer every single PM, and you are free to ask anything anytime.

Share this post


Link to post
Share on other sites

This webserver is great and still runs well today,
However I have found some issues with the _HTTP_SendData function also, and I thought it worth mentioning in case others experience the same issue.

The while loop in HTTP_SendData will loop forever if a network issue occurs while the data is being sent,
because there is no error check on the TCPSend.

I added a line below
$a = TCPSend($hSocket, $bData) ; TCPSend returns the number of bytes sent

;this is the additional line:
If $a = 0 Then ExitLoop

that way it will break the loop if it fails to send data or an error occurs.

(I would run the server, serving a page that was refreshed by the client every 15 seconds. The server would always hang every 4-8 hours, this resolved my issue)

Also, if you want to find the IP address of the client connecting to your webserver, this code will do it (not my code)
Useful for blacklisting or filtering certain client IP addresses

;used in combination with the other autoit TCP functions
;such as TCP_Listen, this finds the clients ip address
;via the TCP socket handle
Func _TCP_Server_ClientIP($hSocket)
    Local $pSocketAddress, $aReturn
    $pSocketAddress = DllStructCreate("short;ushort;uint;char[8]")
    $aReturn = DllCall("ws2_32.dll", "int", "getpeername", "int", $hSocket, "ptr", DllStructGetPtr($pSocketAddress), "int*", DllStructGetSize($pSocketAddress))
    If @error Or $aReturn[0] <> 0 Then Return $hSocket
    $aReturn = DllCall("ws2_32.dll", "str", "inet_ntoa", "int", DllStructGetData($pSocketAddress, 3))
    If @error Then Return $hSocket
    $pSocketAddress = 0
    Return $aReturn[0]
EndFunc ;==>_TCP_Server_ClientIP

 

Share this post


Link to post
Share on other sites
On 7/2/2015 at 9:05 AM, minxomat said:
Func _HTTP_SendData($hSocket, $bData, $sMimeType, $sReply = "200 OK")
    $sPacket = Binary("HTTP/1.1 " & $sReply & @CRLF & _
            "Server: " & $sServerName & @CRLF & _
            "Connection: close" & @CRLF & _
            "Content-Lenght: " & BinaryLen($bData) & @CRLF & _
            "Content-Type: " & $sMimeType & @CRLF & _
            @CRLF)
    TCPSend($hSocket, $sPacket) ; Send start of packet
    ConsoleWrite("# [HTML] Sent header." & @LF)

    Local $iLastData = -1 ; buffer to check packet flood

    While BinaryLen($bData) ; Send data in chunks (most code by Larry)
        $a = TCPSend($hSocket, $bData) ; TCPSend returns the number of bytes sent
        $bData = BinaryMid($bData, $a + 1, BinaryLen($bData) - $a)
        If BinaryLen($bData) = $iLastData And $iLastData >= 0 Then ExitLoop
        $iLastData = BinaryLen($bData)
    WEnd

    If Not $iLastData Then
        $sPacket = Binary(@CRLF & @CRLF) ; Finish the packet
        TCPSend($hSocket, $sPacket)
        ConsoleWrite("# [HTML] Sent everything" & @LF)
    EndIf

    TCPCloseSocket($hSocket)
    ConsoleWrite("# [HTML] Closed connection." & @LF)
EndFunc   ;==>_HTTP_SendData

 

I have seen this code section in several scripts over the years, and I just wanted to make clear that you cannot chunk data this way. Once $bData is in the TCPSend buffer, there is nothing else left over to chunk in the above script. TCP internally, will send the data at the pace it's being received on the other end.

In order to do a proper chunk, you would need to split the data into sections before sending it.

Otherwise, this will suffice to send it all at once - at the same time...

Func _HTTP_SendData($hSocket, $bData, $sMimeType, $sReply = "200 OK")
    Local $sPacket = Binary("HTTP/1.1 " & $sReply & @CRLF & _
            "Server: " & $sServerName & @CRLF & _
            "Connection: close" & @CRLF & _
            "Content-Length: " & BinaryLen($bData) & @CRLF & _; <== Content-Length was misspelled
            "Content-Type: " & $sMimeType & @CRLF & @CRLF & $bData & @CRLF & @CRLF)
    TCPSend($hSocket, $sPacket); Send everything at once
EndFunc

 

 


"The mediocre teacher tells. The Good teacher explains. The superior teacher demonstrates. The great teacher inspires." -William Arthur Ward

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...