Jump to content

HttpApi UDF - HTTP Server API


TheXman
 Share

Recommended Posts

I think you are headed in the right direction.  Let me know how it goes or if I can help.  If you come up with new or modified functionality that would benefit others, I would definitely consider adding to the HTTPAPI UDF lib.  :)

Edited by TheXman
Link to comment
Share on other sites

  • 3 months later...

Been using this for a personal project, love it:)

Just had a suggestion:
In the function _HTTPAPI_HttpSendHttpResponse adding an additional parameter $sResponseType

Func _HTTPAPI_HttpSendHttpResponse($hRequestQueue, $iRequestId, $iStatusCode, $sReason, $sBody, $sResponseType = "text/html")

And replacnig "text/html" in the function with $sResponseType.
Allows one to use other formats, like JSON :)
For example:

Switch $sPath
    Case $path & "/username"
    $iStatusCode = 200
    $sReason     = "OK"
    $sMsg        = '{"username":"' & @UserName & '"}'
                        
EndSwitch
                
_HTTPAPI_HttpSendHttpResponse($requestqueue, $tRequest.RequestId, $iStatusCode, $sReason, $sMsg, "application/json")

 

Link to comment
Share on other sites

Thank you @noellarkin, I'm glad you've found the UDF useful.  :)

I think your suggestion of being able to set the "Content-Type" response header to something other than "text/html" is an excellent one.  Although a "Content-Type" of "text/html" would work for JSON and most other content types, I can definitely think of reasons why having that header set to something more specific would be beneficial on the client side.

I will implement your feature request of being able to set the "Content-Type" header value in the call to _HTTPAPI_HttpSendHttpResponse().  Since it is now morning where I am, I should be able to post the updated version later today.

Thanks :)

Link to comment
Share on other sites

What's New in Version v1.4.0

  • v1.4.0 (2023-08-15)
    • Added the ability to set the Content-Type response header's value in _HTTPAPI_HttpSendHttpResponse().  See the function's header and the example script in order to see how to implement the new functionality.  Thanks @noellarkin for the suggestion.
    • Added an additional api link (/json) to the example script in order to show the new content-type functionality in _HTTPAPI_HttpSendHttpResponse().
       
  • v1.3.0 (2022-06-14) - These modifications were not previously released.
    • Added 2 new internal functions related to setting response headers:
      • __HTTPAPI_ResetKnownResponseHeaderValue() - Clears a known response header
      • __HTTPAPI_SetKnownResponseHeaderValue()   - Set a known response header
      • Did a little code optimization/refactoring in _HTTPAPI_HttpSendHttpResponse() and some of the struct definitions.
Edited by TheXman
Link to comment
Share on other sites

Glad I could help :)
 

I was working on this today and I wrote some helper functions for HTTP API as well, perhaps some of these will be useful.
Helpers:
_HTTPAPI_BindGroupIDRequestQueue, _HTTPAPI_UnbindGroupIDRequestQueue and _HTTPAPI_CaptureCmd are directly from your example script.

_HTTPAPI_CreateRequestCaseArray and _HTTPAPI_AddRequestCase make it easier to add different cases, replacing the switch/case with a search function.

_HTTPAPI_ProcessRequests does exactly that. _HTTPAPI_Run abstracts away everything but the essentials.

These are the functions:

#include-once

Func _HTTPAPI_BindGroupIDRequestQueue($groupid, $requestqueue)


    Local $FunctionName = _DebugEX_StartFunc("_HTTPAPI_BindGroupIDRequestQueue")
    Local $ReturnValue = 0

    Local $BindingInfo = DllStructCreate($__HTTPAPI_gtagHTTP_BINDING_INFO)
    $BindingInfo.Flags = $__HTTPAPI_HTTP_PROPERTY_FLAG_PRESENT
    $BindingInfo.RequestQueueHandle = $requestqueue
    
    Local $GroupPropertySet = _HTTPAPI_HttpSetUrlGroupProperty($groupid, $__HTPPAPI_HttpServerBindingProperty, DllStructGetPtr($BindingInfo), DllStructGetSize($BindingInfo))
    If $GroupPropertySet = True Then
        $ReturnValue = 1
    Else
        _DebugEX_ERROR($FunctionName, "GroupPropertySet:" & $GroupPropertySet)
    Endif

    Return $ReturnValue

EndFunc


Func _HTTPAPI_UnbindGroupIDRequestQueue($groupid)

    Local $FunctionName = _DebugEX_StartFunc("_HTTPAPI_UnbindGroupIDRequestQueue")
    Local $ReturnValue = 0

    Local $BindingInfo = DllStructCreate($__HTTPAPI_gtagHTTP_BINDING_INFO)
    $BindingInfo.Flags = 0
    $BindingInfo.RequestQueueHandle = Null
    
    Local $GroupPropertySet = _HTTPAPI_HttpSetUrlGroupProperty($groupid, $__HTPPAPI_HttpServerBindingProperty, DllStructGetPtr($BindingInfo), DllStructGetSize($BindingInfo))
    If $GroupPropertySet = True Then
        $ReturnValue = 1
    Else
        _DebugEX_ERROR($FunctionName, "GroupPropertySet:" & $GroupPropertySet)
    Endif

    Return $ReturnValue
    
EndFunc


Func _HTTPAPI_CaptureCmd($sCmd, $sWorkingDir = @WorkingDir)
    Local $iPID     = 0
    Local $sOutput  = ""

    ;resolve defaults
    If $sWorkingDir = Default Then $sWorkingDir = @WorkingDir

    ;execute command
    $iPID = Run(@ComSpec & ' /c ' & $sCmd, $sWorkingDir, @SW_HIDE, $STDERR_MERGED)
    If @error Then
        ConsoleWrite("Error: Unable to execute command - " & $sCmd & @CRLF)
        Return SetError(1, 0, "")
    EndIf

    ;wait for process to end
    ProcessWaitClose($iPID)

    ;get output
    $sOutput = StdoutRead($iPID)

    ;if no output, set @error and return empty string
    If $sOutput = "" Then Return SetError(2, 0, "")

    ;return output
    Return $sOutput
EndFunc

Func _HTTPAPI_CreateRequestCaseArray()

    ; verb, path, statuscode, reason, messsage, responsetype, execute (0/1)
    Local $ReturnValue[0][7]
    Return $ReturnValue

EndFunc

Func _HTTPAPI_AddRequestCase(ByRef $thisrequestcasearray, $verb, $path, $statuscode, $reason, $message, $responsetype, $execute = 0)

    Local $FunctionName = _DebugEX_StartFunc("_HTTPAPI_AddCase")

    Local $Append[1][7]
    $Append[0][0] = String($verb)
    $Append[0][1] = String($path)
    $Append[0][2] = Int($statuscode)
    $Append[0][3] = String($reason)
    $Append[0][4] = String($message)
    $Append[0][5] = String($responsetype)
    $Append[0][6] = Int($execute)

    _ArrayConcatenate($thisrequestcasearray, $Append)

    Return 1



EndFunc




Func _HTTPAPI_ProcessRequests(ByRef $thisrequestcasearray, $requestqueue)

    Local $FunctionName = _DebugEX_StartFunc("_HTTPAPI_ProcessRequests")

    Local $GETCase[0][6]
    Local $POSTCase[0][6]
    Local $Append[1][6]
    For $i = 0 To _ArrayEnd($thisrequestcasearray)
        $Append[0][0] = $thisrequestcasearray[$i][1]
        $Append[0][1] = $thisrequestcasearray[$i][2]
        $Append[0][2] = $thisrequestcasearray[$i][3]
        $Append[0][3] = $thisrequestcasearray[$i][4]
        $Append[0][4] = $thisrequestcasearray[$i][5]
        $Append[0][5] = $thisrequestcasearray[$i][6]
        Switch $thisrequestcasearray[$i][0]
            Case "GET"
                _ArrayConcatenate($GETCase, $Append)
            Case "POST"
                _ArrayConcatenate($POSTCase, $Append)
        EndSwitch
    Next
    
    Local $tBuffer = ""
    Local $tRequest = ""
    Local $sPath = ""
    Local $QueryString = ""
    Local $RequestBody = ""

    Local $SearchThisArray = ""
    Local $SearchResult = -1
    Local $StatusCode = 0
    Local $Reason = ""
    Local $Message = ""
    Local $ResponseType = ""
    Local $Execute = 0
    Local $FunctionExecuteOutput = 0
    
    While 1
    
        $tBuffer = _HTTPAPI_HttpReceiveHttpRequest($requestqueue)
        $tRequest = DllStructCreate($__HTTPAPI_gtagHTTP_REQUEST_V2 & StringFormat("byte body[%i];", $__HTTPAPI_REQUEST_BODY_SIZE), DllStructGetPtr($tBuffer))
        $sPath =  _WinAPI_GetString($tRequest.pAbsPath)
        $QueryString = _WinAPI_GetString($tRequest.pQueryString)
        $RequestBody = ""
        If $tRequest.pEntityChunks > 0 Then
            $RequestBody = _HTTPAPI_GetRequestBody($tRequest)
            ConsoleWrite(@CRLF & "RequestBody:" & $RequestBody)
        EndIf
        
        If StringRight($sPath, 1) = "/" Then $sPath = StringTrimRight($sPath, 1)
        If _StringContainsSubstring($sPath, "/stop") Then
            _HTTPAPI_HttpSendHttpResponse($requestqueue, $tRequest.RequestId, 200, "OK", "Shutting down API...", "text/plain")
            ConsoleWrite(@CRLF & "Shutting down API...")
            ExitLoop
        Else    
            If $tRequest.Verb = $__HTTPAPI_HttpVerbGET Then
                $SearchThisArray = $GETCase
                ConsoleWrite(@CRLF & "GET")
            EndIf
            If $tRequest.Verb = $__HTTPAPI_HttpVerbPOST Then
                $SearchThisArray = $POSTCase
                ConsoleWrite(@CRLF & "POST")
            EndIf
            $SearchResult = _ArraySearch($SearchThisArray, $sPath, 0, 0, 0, 2, Default, Default, Default)
            If _ArraySearchFound($SearchResult) Then
                ConsoleWrite(@CRLF & "found")
                $StatusCode = $SearchThisArray[$SearchResult][1]
                $Reason = $SearchThisArray[$SearchResult][2]
                $Message = $SearchThisArray[$SearchResult][3]
                $ResponseType = $SearchThisArray[$SearchResult][4]
                $Execute = $SearchThisArray[$SearchResult][5]
                ConsoleWrite(@CRLF & "StatusCode:" & $StatusCode)
                ConsoleWrite(@CRLF & "Reason:" & $Reason)
                ConsoleWrite(@CRLF & "Message:" & $Message)
                ConsoleWrite(@CRLF & "ResponseType:" & $ResponseType)
                ConsoleWrite(@CRLF & "Execute:" & $Execute)
                
                If $Execute Then
                    $FunctionExecuteOutput = Execute($Message)
                    $Message = $FunctionExecuteOutput
                EndIf
                _HTTPAPI_HttpSendHttpResponse($requestqueue, $tRequest.RequestId, $StatusCode, $Reason, $Message, $ResponseType)
            Else
                _HTTPAPI_HttpSendHttpResponse($requestqueue, $tRequest.RequestId, 404, "Not Found", "<h2>Not Found</h2><hr><p>HTTP Error 404. The requested resource is not found.</p>", "text/html")
                ConsoleWrite(@CRLF & "not found")
            EndIf
            
            $tBuffer = 0
            Sleep(100)
        EndIf
    WEnd

EndFunc


Func _HTTPAPI_Run($host, $path, $requestcases)

    Local $FunctionName = _DebugEX_StartFunc("_HTTPAPI_Run")
    Local $ReturnValue = 1

    Local $EnvironmentInitialized = _HTTPAPI_Startup(True)
    If $EnvironmentInitialized Then
        Local $ServerInitialized = _HTTPAPI_HttpInitialize()
        If $ServerInitialized Then
            Local $ServerSessionID = _HTTPAPI_HttpCreateServerSession()
            If $ServerSessionID <> 0 Then
                Local $URLGroupID = _HTTPAPI_HttpCreateUrlGroup($ServerSessionID)
                If $URLGroupID <> 0 Then
                    Local $RequestQueue = _HTTPAPI_HttpCreateRequestQueue()
                    If $RequestQueue <> 0 Then
                        Local $GroupQueueBound = _HTTPAPI_BindGroupIDRequestQueue($URLGroupID, $RequestQueue)
                        If $GroupQueueBound Then
                            Local $BaseURLAdded = _HTTPAPI_HttpAddUrlToUrlGroup($URLGroupID, $host & $path)
                            If $BaseURLAdded Then
                                _HTTPAPI_ProcessRequests($requestcases, $RequestQueue)
                                
                                OnAutoItExitRegister(_HTTPAPI_HttpRemoveUrlFromUrlGroup($URLGroupID, "", $__HTTPAPI_HTTP_URL_FLAG_REMOVE_ALL))
                            Else
                                _DebugEX_ERROR($FunctionName, "BaseURLAdded:" & $BaseURLAdded)
                            EndIf
                            OnAutoItExitRegister(_HTTPAPI_UnbindGroupIDRequestQueue($URLGroupID))
                        Else
                            _DebugEX_ERROR($FunctionName, "GroupQueueBound:" & $GroupQueueBound)
                        EndIf
                        OnAutoItExitRegister(_HTTPAPI_HttpShutdownRequestQueue($RequestQueue))
                    Else
                        _DebugEX_ERROR($FunctionName, "RequestQueue:" & $RequestQueue)
                    EndIf
                Else
                    _DebugEX_ERROR($FunctionName, "URLGroupID:" & $URLGroupID)
                EndIf
                OnAutoItExitRegister(_HTTPAPI_HttpCloseServerSession($ServerSessionID))
            Else
                _DebugEX_ERROR($FunctionName, "ServerSessionID:" & $ServerSessionID)
            EndIf
            OnAutoItExitRegister(_HTTPAPI_HttpTerminate())
        Else
            _DebugEX_ERROR($FunctionName, "ServerInitialized:" & $ServerInitialized)
        EndIf
    Else
        _DebugEX_ERROR($FunctionName, "EnvironmentInitialized:" & $EnvironmentInitialized)
    EndIf

    Return $ReturnValue

EndFunc

 

An example use case:
 

#AutoIt3Wrapper_AU3Check_Parameters=-w 3 -w 4 -w 5 -w 6 -d

#include <HTTPAPI.au3>

_DebugEX_Console("ENABLE")


Local $HostURL = "http://127.0.0.1:9000"
Local $Path = "/vm"

Local $RequestCases = _HTTPAPI_CreateRequestCaseArray()
_HTTPAPI_AddRequestCase($RequestCases, "GET", $Path, 200, "OK", "<h1>Hello World</h1>", "text/html")
_HTTPAPI_AddRequestCase($RequestCases, "GET", $Path & "/username", 200, "OK", '{"username":"' & @UserName & '"}', "application/json")
_HTTPAPI_AddRequestCase($RequestCases, "POST", $Path & "/requestbody", 200, "OK", '$RequestBody', "text/plain", 1)
_HTTPAPI_AddRequestCase($RequestCases, "GET", $Path & "/python", 200, "OK", '_HTTPAPI_CaptureCmd("python helloworld.py", "C:\AutoIt\CommunityLibraries\TheXman\HTTPAPI\examples")', "text/plain", 1)

_HTTPAPI_Run($HostURL, $Path, $RequestCases)

 

 

Note: the code has some functions that I use internally for work, you may want to add them in so there aren't any errors:

Global $DEBUG_CONSOLE = 0
Global $DEBUG_COUNTER_ERROR = 0
Global $DEBUG_COUNTER_WARNING = 0

Func _ArraySearchFound($arraysearchresult)

    Local $FunctionName = "_ArraySearchFound"
    Local $ReturnValue = 0
    
    If $arraysearchresult <> -1 Then $ReturnValue = 1
    
    Return $ReturnValue
    
EndFunc

Func _ArrayEnd(ByRef $thisarray)

    Local $FunctionName = "_ArrayEnd"
    Local $ReturnValue = -1
    
    $ReturnValue = UBound($thisarray) - 1
    
    Return $ReturnValue
    
EndFunc


Func _StringContainsSubstring($string, $substring)

    Local $ReturnValue = 0

    If StringInStr(String($string), String($substring)) <> 0 Then $ReturnValue = 1

    Return $ReturnValue

EndFunc

Func _DebugEX_ConsoleWrite($thisfunction, $string, $guid = "")

    Local $ConsoleWriteGUID = ""
    If $guid <> "" Then $ConsoleWriteGUID = "::::" & $guid
    
    Local $ConsoleWriteString = "::::" & $string
    
    Local $ConsoleText = @CRLF & $thisfunction & $ConsoleWriteGUID & $ConsoleWriteString

    If $DEBUG_CONSOLE Then ConsoleWrite($ConsoleText)
    
    If _StringContainsSubstring($string, "ERROR ") Then $DEBUG_COUNTER_ERROR += 1
    If _StringContainsSubstring($string, "WARNING ") Then $DEBUG_COUNTER_WARNING += 1   
    
EndFunc

Func _DebugEX_Console($switch)

    Switch $switch
        Case "ENABLE"
            $DEBUG_CONSOLE = 1

        Case "DISABLE"
            $DEBUG_CONSOLE = 0
    EndSwitch

EndFunc

Func _DebugEX_StartFunc(ByRef $thisfunction)

    Local $ConsoleText = @CRLF & @CRLF & "starting " & $thisfunction & "..."
    If $DEBUG_CONSOLE Then ConsoleWrite($ConsoleText)
    Return $thisfunction
    
EndFunc

Func _DebugEX_ERROR($thisfunction, $string, $guid = "")
    $string = "ERROR " & $string
    _DebugEX_ConsoleWrite($thisfunction, $string, $guid)
EndFunc

Func _DebugEX_WARNING($thisfunction, $string, $guid = "")
    $string = "WARNING " & $string
    _DebugEX_ConsoleWrite($thisfunction, $string, $guid)
EndFunc

 

The code is pretty rough, I haven't polished it up much, but I like the RequestCase array thing :)

Link to comment
Share on other sites

Can this code be forked ?, so that I receive in one script and handle the request in another script ?
The reason been that:
1) Say I get a "database query" request and return a value. That is a fast transaction and need not be forked.
2) Say I get a "big file" request and return a value. That is a slow transaction and need to be forked.
 

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

5 minutes ago, noellarkin said:

I wrote some helper functions for HTTP API as well, perhaps some of these will be useful.

Thanks, I will definitely check them out.  :)

Link to comment
Share on other sites

3 minutes ago, argumentum said:

Can this code be forked ?, so that I receive in one script and handle the request in another script ?

If I understand what you are asking for correctly, it seems similar to what @sylremo described HERE.  His next post seemed to be a way to handle that type of processing.  Personally, I haven't really looked into it because I haven't had a need for it.  But at first glance, his proposed solution seemed to be a viable one.

Link to comment
Share on other sites

  • 9 months later...

This is reaaaaly useful! Thank you!

I have a question on this: How do I get the IP of the remote request? looks like it is enumerated as "Transport Address Structure", but I am not sure how to get the value of the ptr on the server. It returns a null value.

Link to comment
Share on other sites

Posted (edited)

Thanks @KarelM

Assuming you are using the example script that is provided with the HTTPAPI UDF, you can add the following function to the bottom.  The function takes a pointer to the SOCKADDR address structure within the request structure as input, which is in $tRequest.pRemoteAddress, and returns the formatted IPv4 or IPv6 address.

; #FUNCTION# ====================================================================================================================
; Name ..........: _SockAddrPtrToIP
; Description ...: Return formatted IPv4 or IPv6 address from pointer to SOCKADDR structure.
; Syntax ........: _HTTPAPI_SocketPtrToIP($pAddress)
; Parameters ....: $pAddress            Pointer to SOCKADDR structure.
; Return values .: Success:             Formatted IP address string.
;                  Failure:             Empty string and sets the @error flag to non-zero
;                  @error:              1 - DllStructCreate failed to create SOCKADDR struct
;                                       2 - Unrecognized address family (AF) value.
;                                       3 - DllCall to inet_ntop failed
; Author ........: TheXman
; ===============================================================================================================================
Func _SockAddrPtrToIP($pAddress)
    Local $aResult
    Local $tSOCKADDR
    Local $tStringBuffer = DllStructCreate("char string[47];")

    Const $AF_INET  = 2
    Const $AF_INET6 = 23

    Const $tagSOCKADDR = _
        "short   inFamily;" & _
        "byte    data[14];"

    Const $tagSOCKADDR_IN = _
        "short   inFamily;" & _
        "ushort  inPort;"   & _
        "byte    inAddr[4];"   & _
        "byte    reserved[8];"

    Const $tagSOCKADDR_IN6 = _
        "short   inFamily;"    & _
        "ushort  inPort;"      & _
        "ulong   inFlowInfo;"  & _
        "byte    inAddr[16];"  & _
        "ulong   scopeId;"


    ; Create struct
    $tSOCKADDR = DllStructCreate($tagSOCKADDR, $pAddress)
    If @error Then Return SetError(1, 0, "")

    ; If address is IPv4
    If $tSOCKADDR.inFamily = $AF_INET Then
        ; Create IPv4 struct
        $tSOCKADDR = DllStructCreate($tagSOCKADDR_IN, $pAddress)
    ElseIf $tSOCKADDR.inFamily = $AF_INET6 Then
        ; Create IPv6 struct
        $tSOCKADDR = DllStructCreate($tagSOCKADDR_IN6, $pAddress)
    Else
        Return SetError(2, 0, "")
    EndIf

    ; Get formatted IP address
    $aResult = _
        DllCall("Ws2_32.dll", "str", "inet_ntop", _
            "int",     $tSOCKADDR.inFamily, _
            "ptr",     DllStructGetPtr($tSOCKADDR, "inAddr"), _
            "struct*", $tStringBuffer, _
            "int",     DllStructGetSize($tStringBuffer) _
        )
    If @error Then Return SetError(3, 0, "")

    Return $aResult[0]

EndFunc

 

If you add the following CASE to the process_get_request function in the example script, it will show you how you can invoke the function above:

;/remote addr
Case $HTTP_PATH & "/remoteaddr"
    $iStatusCode = 200
    $sReason     = "OK"

    $sAddr = _SockAddrPtrToIP($tRequest.pRemoteAddress)
    If @error Then ConsoleWrite("Bad return from _SockAddrPtrToIP - @error = " & @error & @CRLF)

    $sMsg = "<h3>GET request received!</h3>" & _
            "Remote address: " & $sAddr & "<br>"

 

When you use  "http://127.0.0.1:9000/a3server/remoteaddress", it should give you a response like:

image.png.58c65daba52c602d5aa2e1e6526c052d.png

 

 

Edited by TheXman
Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...