Jump to content
jvanegmond

Powerful HTTP Server in Pure AutoIt

Recommended Posts

So yeah, definitely a cool server. I finally built the web app I've been wanting to do. Took me all day, but on the plus side I tweaked the server a bit, maybe you'll like some of the changes. The idea was to build a small webapp that I could use to start and stop the Simplify Media server on my computer. I'd leave it running all the time, but the damn thing is always using like 3% CPU, which keeps the computer for doing things it should be doing during idle time. And there's no damn reason it should be consuming any CPU just idling, but the devs assured me it's normal....I digress.

So the webapp is secured on my internal network, behind my VPN. I haven't looked into the session server yet, so I didn't want to make it web facing. I access it from my iPhone via VPN to start Simplify Media when I want to use it. Basically it's two big buttons and text displaying the current status of the server. I love being able to use AutoIt to do whatever I want on the server. It's much easier than learning PHP.

Where I ran into problems was using the iPhone over the VPN. It's a slower connection (3G + VPN) and I was losing POST data on the server end. It was receiving the header and showing the correct Content Length, but the actual data was lost. I finally figured out why, and came up with two solutions. The first thing that's absolutely necessary is to set Opt("TcpTimeout", ) to some higher value, like 1 second, before entering the $aSocket For loop. The second part is to make sure all the data is received. First I did it by looping TCPRecv for each socket until no more data was received. However this defeated the purpose of your array and multiple connections. So I settled on keeping your scheme, but detecting the end of the data differently. Your original script looks for @LF & @LF, but that doesn't work for me since there are two @LF's between the end of the header and the actual POST data. Instead I'm waiting for a TCPRecv with empty data returned. Along with the timeout, this was working perfectly for me even over the slow iPhone connection. It turned out the lost POST data before was because the data was being received over two TCPRecv calls, but the @LF & @LF was detecting the wrong end of the data. Here's the main loop I settled on which is working well. I also switched most string tests to StringRegExp().

Thanks so much for this framework, it's really flexible. Loving it!

**snip**

Edited by wraithdu

Share this post


Link to post
Share on other sites

Your original script looks for @LF & @LF, but that doesn't work for me since there are two @LF's between the end of the header and the actual POST data. Instead I'm waiting for a TCPRecv with empty data returned. Along with the timeout, this was working perfectly for me even over the slow iPhone connection. It turned out the lost POST data before was because the data was being received over two TCPRecv calls, but the @LF & @LF was detecting the wrong end of the data. Here's the main loop I settled on which is working well. I also switched most string tests to StringRegExp().

Thanks so much for this framework, it's really flexible. Loving it!

In all fairness: I expected that problem to come around some time, the correct solution is to parse the Content-Length header. However, your solution is a very clever one! I didn't think of doing it like that myself, it's simple and effective.

Thank you for the compliment. : )

Share this post


Link to post
Share on other sites

Funny you should say that... I thought more about it while I was sleeping (yeah, I'm not normal sometimes), and I came to the same conclusion. Here's my re-re-tweaked version that does just that for POST requests. The GET requests always seem to come in one packet. It allowed my to drop the TcpTimeout setting to 250ms (technically we could leave it at default, but a slightly larger timeout helps with slow connections). I found while testing that at 250ms, the server may not get the POST data from my iPhone until the 2nd or 3rd loop after it receives the header, which means even my scheme above would lose the POST data. I'm gonna post the whole thing this time since I've made some changes all over, so you can get the bigger picture.

#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

#include <_OsVersionInfo.au3>
#include <_SysTray.au3>

Opt("TcpTimeout", 250)

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

; HTML Template
Global $indexhtml
Global $template = FileRead(@ScriptDir & "\template.html")
Global $aSocket[$iMaxUsers] ; Creates an array to store all the possible users
Global $sBuffer[$iMaxUsers] ; All these users have buffers when sending/receiving, so we need a place to store those
Global $Win7OrHigher = _OsVersionTest($VER_GREATER_EQUAL, 6, 1)

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
            $sBuffer[$x] = ""
            ContinueLoop ; Go to the next iteration of the loop, not really needed but looks oh so good
        Else ; data received
;~             ConsoleWrite("sNewData:-----||" & $sNewData & "||" & @CRLF)
            $sBuffer[$x] &= $sNewData ;store it in the buffer
            If $sBuffer[$x] Then ; the request has data
                $sRequestType = StringRegExp($sBuffer[$x], "(POST|GET)\s", 1) ; gets the type of the request
                If Not @error Then ; we have a valid request
                    If $sRequestType[0] = "GET" Then ; user wants to download a file or whatever ..
                        $sRequest = StringRegExp($sBuffer[$x], "GET\s+?(.+?)\s", 1) ; let's see what file he actually wants
                        $sRequest = $sRequest[0]
                        If StringInStr(StringReplace($sRequest, "\", "/"), "/.") Then ; Disallow any attempts to go back a folder
                            _HTTP_SendError($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
                            $sRequest = StringReplace($sRequest, "/", "\") ; convert HTTP slashes to windows slashes, not really required because windows accepts both
                            If StringRight($sRequest, 11) = "\index.html" Then
                                ; prepare index.html
                                _PrepareIndex()
                                _HTTP_SendHTML($aSocket[$x], $indexhtml)
                            Else
                                _HTTP_SendFileNotFoundError($aSocket[$x]) ; File does not exist, so we'll send back an error..
                            EndIf
                        EndIf
                    ElseIf $sRequestType[0] = "POST" Then ; user has come to us with data, we need to parse that data and based on that do something special
                        ; check if we have all the data
                        $sBuff = StringStripCR($sBuffer[$x]) ; strip @CR
                        $sLen = StringRegExp($sBuff, "Content-Length: (\d+?)\n", 1)
                        If Not @error And ($sLen[0] > 0) Then
                            ; we have content
                            If StringRegExp($sBuff, "\n{2}.{" & $sLen[0] & "}\z") Then
                                ; we have received all the content
                                $sPostTarget = StringRegExp($sBuffer[$x], "POST\s+?(.+?)\s", 1)
                                If $sPostTarget[0] = "/index.html" Then ; only allow post from /index.html
                                    $aPOST = _HTTP_GetPost($sBuffer[$x]) ; parses the post data
                                    $sButton1 = _HTTP_POST("button1", $aPOST) ; Like PHPs _POST, but it requires the second parameter to be the return value from _Get_Post
                                    $sButton2 = _HTTP_POST("button2", $aPOST)

                                    If $sButton1 Then
                                        ; start Simplify Media
                                        Run('"' & @ProgramFilesDir & '\Simplify Media\SimplifyMedia.exe"', @ProgramFilesDir & '\Simplify Media')
                                        ProcessWait("SimplifyMedia.exe", 10)
                                    ElseIf $sButton2 Then
                                        $PID = ProcessExists("SimplifyMedia.exe")
                                        ; remove tray icon
                                        For $i = (_SysTrayIconCount() - 1) To 0 Step -1
                                            $hwnd = _SysTrayIconHandle($i)
                                            $proc = WinGetProcess($hwnd)
                                            If $hwnd And ($proc = $PID) Then _SysTrayIconRemove($i)
                                        Next
                                        If $Win7OrHigher Then
                                            ; NotifyIconOverflowWindow
                                            For $i = (_SysTrayIconCount(2) - 1) To 0 Step -1
                                                $hwnd = _SysTrayIconHandle($i, 2)
                                                $proc = WinGetProcess($hwnd)
                                                If $hwnd And ($proc = $PID) Then _SysTrayIconRemove($i, 2)
                                            Next
                                        EndIf
                                        ; stop simplify Media
                                        ProcessClose("SimplifyMedia.exe")
                                        ProcessWaitClose("SimplifyMedia.exe", 10)
                                    EndIf
                                    _PrepareIndex()
                                    _HTTP_SendHTML($aSocket[$x], $indexhtml)
                                Else
                                    ; bad request
                                    _HTTP_SendError($aSocket[$x], "Bad request.")
                                EndIf
                            Else
                                ; we don't have all the content in the buffer yet
                                ContinueLoop
                            EndIf
                        Else
                            ; no content, discard the the request, but do not error
                            _PrepareIndex()
                            _HTTP_SendHTML($aSocket[$x], $indexhtml)
                        EndIf
                    EndIf
                Else
                    ; bad request
                    _HTTP_SendError($aSocket[$x], "Bad request.")
                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 _PrepareIndex()
    If ProcessExists("SimplifyMedia.exe") Then
        ; running
        $indexhtml = StringRegExpReplace($template, '(value="button1")', "\1 disabled")
        $indexhtml = StringRegExpReplace($indexhtml, 'statusgoeshere', "running")
    Else
        ; not running
        $indexhtml = StringRegExpReplace($template, '(value="button2")', "\1 disabled")
        $indexhtml = StringRegExpReplace($indexhtml, 'statusgoeshere', "stopped")
    EndIf
EndFunc

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_SendError($hSocket, $sErr = $404error)
    _HTTP_SendHTML($hSocket, $sErr)
EndFunc   ;==>_HTTP_SendError

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

Func _HTTP_GetPost($s_Buffer) ; parses incoming POST data
    Local $sTempPost, $sPostData, $sTemp
    ; Create the base struck
    $sPostData = StringRegExp($s_Buffer, "[\r\n](.+?)\z", 1)
    $sPostData = StringSplit($sPostData[0], "&")

    Local $sReturn[$sPostData[0] + 1][2]
    For $t = 1 To $sPostData[0]
        $sTemp = StringSplit($sPostData[$t], "=")
        If $sTemp[0] >= 2 Then
            $sReturn[$t][0] = $sTemp[1]
            $sReturn[$t][1] = $sTemp[2]
        EndIf
    Next

    Return $sReturn
EndFunc   ;==>_HTTP_GetPost

Func _HTTP_Post($sName, $sArray) ; Returns a POST variable like a associative array.
    For $i = 1 To UBound($sArray) - 1
        If $sArray[$i][0] = $sName Then
            Return $sArray[$i][1]
        EndIf
    Next
    Return ""
EndFunc   ;==>_HTTP_Post

Share this post


Link to post
Share on other sites

Sorry if this is a silly question, but what's the username and password needed?

I've tried every variation of blank, pass, auth, and user imaginable.

Share this post


Link to post
Share on other sites

If your talking about the Session server, there should be an auth.ini (in the same folder as the script). The contents should be in the form of:

[auth]
user=pass

Adding other users is simple by adding more like user=pass.

Cheers,

Brett

Share this post


Link to post
Share on other sites

Hi,

this server is just cool and perfect for my current project :-)

But as I am not too experiered with tcp I failed making the server accessable for localhost only.

What/Where do I have make changes ?

Greetings

SlowlyDead

Edit:

Changed ip to 127.0.0.1 and it works, but is there a more elegant way giving the choise (by variable) which clients are allowed ?

Edited by SlowlyDead

Share this post


Link to post
Share on other sites

As to the problems I was having: I failed to edit the file locations in the .ini. Set them to the correct location and everything works as it should.

Excellent job, guys :mellow:

Share this post


Link to post
Share on other sites

SlowlyDead,

You could probably whitelist/blacklist certain IPs like so by adding the following statement into the original while loop.

Dim $iWhiteList = "127.0.0.1|192.168.0.*"

If $iNewSocket >= 0 Then ; Verifies that there actually is an incoming connection
        $newip = SocketToIP($iNewSocket)
        ConsoleWrite ($newip & @CRLF)
        If StringRegExp ($newip, "(" & $iWhiteList & ")") = 0 Then
            MsgBox (0, "", "OMGGGGGGGGGG!")
            TCPCloseSocket ($iNewSocket)
            ContinueLoop
        EndIf
        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

It is untested fully so I'm not 100% if it will work like it should. If you set the server to listen on "127.0.0.1" then logically the local computer will be the only one able to access it.

The extra function can be found in the example for TCPRecv.

Share this post


Link to post
Share on other sites

I’ve trying to make this (fantastic autoit) webserver work with upload file from a browser to the webserver but I just can get to work.

Is there some one how know how to this? It’s driving me mad.

I use this html script:

<form enctype="multipart/form-data" method="post">

Type some text :<br>

<input type="text" name="textline" size="30">

<p>

Please specify a file, or a set of files:<br>

<input type="file" name="datafile" size="40">

</p>

<div>

<input type="submit" value="Verzenden" name="B1">

</div>

</form>

Share this post


Link to post
Share on other sites

I am attempting to do the exact same thing! My end goal is to be able to upload files through the autoit webserver, and then print them at home. A free, universal webbased remote print to home to any printer solution. You're about to make my brain explode with autoit webserver ideas.

Share this post


Link to post
Share on other sites

I am attempting to do the exact same thing! My end goal is to be able to upload files through the autoit webserver, and then print them at home. A free, universal webbased remote print to home to any printer solution. You're about to make my brain explode with autoit webserver ideas.

Cool idea! I was just thinking about this server again a few days back and realized how much potential it has. Everything needs a webserver! I think I may look into easy integration of a REST/SOAP Server written in AutoIt into existing applications. That'd be winnar!

Share this post


Link to post
Share on other sites

Hmmm... for some reason I can't get this to work.

When I run the program it tells me the server was created at Http://0.0.0.0/ but if I navigate there nothing happens. And with the example above nothing happens either :/


MCR.jpg?t=1286371579

Most recent sig. I made

Quick Launcher W/ Profiles Topic Movie Database Topic & Website | LiveStreamer Pro Website | YouTube Stand-Alone Playlist Manager: Topic | Weather Desktop Widget: Topic | Flash Memory Game: Topic | Volume Control With Mouse / iTunes Hotkeys: Topic | Weather program: Topic | Paws & Tales radio drama podcast mini-player: Topic | Quick Math Calculations: Topic

Share this post


Link to post
Share on other sites

Hmmm... for some reason I can't get this to work.

When I run the program it tells me the server was created at Http://0.0.0.0/ but if I navigate there nothing happens. And with the example above nothing happens either :/

Hey Damien, the web server gets the local IP address by using the AutoIt macro @IpAddress1. You can find that part in the first few lines of the code. For some reason, @IpAddress1 doesn't return the correct local IP on your computer. You can try updating your version of AutoIt or if that doesn't help you can change that part in the code to your local IP, like: "192.168.0.100". Or you can change the IP in the code in the first place. Anyway, the first few lines is the part you want to look.

Share this post


Link to post
Share on other sites

Hello

help me D:

I'm using Fastweb so I don't have an pubblic IP. I succeed to get an PUBBLIC IPv6 address and it works (http://www.berkom.blazing.de/tools/ping.cgi and write: ren83.broker.freenet6.net).

Now, if i put my WORKING ipv6 ip on

Local $sIP = 2001:5c0:1400:b::95bf ;; <- this is my ipv6

(2001:5c0:1400:b::95bf is my ipv6)

i get error on creating socket.

How can i create server with ipv6?

Thank you.

ps: Sorry for my bad english.

ps2: freenet6 provides a "website" (?) called: YOUR_USERNAME.broker.freenet6.net. My is ren83.broker.freenet6.net. Could it helps?

Edited by seicaratteri

Share this post


Link to post
Share on other sites
You are mixing up the MAC address with the IP address. try a "ping ren83.broker.freenet6.net" and you will see you get 81.171.72.11 as IP address. Edited by 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

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...