TheXman Posted May 6, 2023 Author Posted May 6, 2023 (edited) 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 May 6, 2023 by TheXman sylremo, argumentum and noellarkin 3 CryptoNG UDF: Cryptography API: Next Gen jq UDF: Powerful and Flexible JSON Processor | jqPlayground: An Interactive JSON Processor Xml2Json UDF: Transform XML to JSON | HttpApi UDF: HTTP Server API | Roku Remote: Example Script About Me How To Ask Good Questions On Technical And Scientific Forums (Detailed) | How to Ask Good Technical Questions (Brief) "Any fool can know. The point is to understand." -Albert Einstein "If you think you're a big fish, it's probably because you only swim in small ponds." ~TheXman
noellarkin Posted August 15, 2023 Posted August 15, 2023 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") Musashi and Hashim 2
TheXman Posted August 15, 2023 Author Posted August 15, 2023 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 Hashim 1 CryptoNG UDF: Cryptography API: Next Gen jq UDF: Powerful and Flexible JSON Processor | jqPlayground: An Interactive JSON Processor Xml2Json UDF: Transform XML to JSON | HttpApi UDF: HTTP Server API | Roku Remote: Example Script About Me How To Ask Good Questions On Technical And Scientific Forums (Detailed) | How to Ask Good Technical Questions (Brief) "Any fool can know. The point is to understand." -Albert Einstein "If you think you're a big fish, it's probably because you only swim in small ponds." ~TheXman
TheXman Posted August 15, 2023 Author Posted August 15, 2023 (edited) 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 August 15, 2023 by TheXman noellarkin, Hashim, argumentum and 1 other 4 CryptoNG UDF: Cryptography API: Next Gen jq UDF: Powerful and Flexible JSON Processor | jqPlayground: An Interactive JSON Processor Xml2Json UDF: Transform XML to JSON | HttpApi UDF: HTTP Server API | Roku Remote: Example Script About Me How To Ask Good Questions On Technical And Scientific Forums (Detailed) | How to Ask Good Technical Questions (Brief) "Any fool can know. The point is to understand." -Albert Einstein "If you think you're a big fish, it's probably because you only swim in small ponds." ~TheXman
noellarkin Posted August 15, 2023 Posted August 15, 2023 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: expandcollapse popup#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: expandcollapse popupGlobal $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
argumentum Posted August 15, 2023 Posted August 15, 2023 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.
TheXman Posted August 15, 2023 Author Posted August 15, 2023 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. noellarkin 1 CryptoNG UDF: Cryptography API: Next Gen jq UDF: Powerful and Flexible JSON Processor | jqPlayground: An Interactive JSON Processor Xml2Json UDF: Transform XML to JSON | HttpApi UDF: HTTP Server API | Roku Remote: Example Script About Me How To Ask Good Questions On Technical And Scientific Forums (Detailed) | How to Ask Good Technical Questions (Brief) "Any fool can know. The point is to understand." -Albert Einstein "If you think you're a big fish, it's probably because you only swim in small ponds." ~TheXman
TheXman Posted August 15, 2023 Author Posted August 15, 2023 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. argumentum 1 CryptoNG UDF: Cryptography API: Next Gen jq UDF: Powerful and Flexible JSON Processor | jqPlayground: An Interactive JSON Processor Xml2Json UDF: Transform XML to JSON | HttpApi UDF: HTTP Server API | Roku Remote: Example Script About Me How To Ask Good Questions On Technical And Scientific Forums (Detailed) | How to Ask Good Technical Questions (Brief) "Any fool can know. The point is to understand." -Albert Einstein "If you think you're a big fish, it's probably because you only swim in small ponds." ~TheXman
KarelM Posted May 17, 2024 Posted May 17, 2024 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.
TheXman Posted May 17, 2024 Author Posted May 17, 2024 (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. expandcollapse popup; #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: Edited May 17, 2024 by TheXman CryptoNG UDF: Cryptography API: Next Gen jq UDF: Powerful and Flexible JSON Processor | jqPlayground: An Interactive JSON Processor Xml2Json UDF: Transform XML to JSON | HttpApi UDF: HTTP Server API | Roku Remote: Example Script About Me How To Ask Good Questions On Technical And Scientific Forums (Detailed) | How to Ask Good Technical Questions (Brief) "Any fool can know. The point is to understand." -Albert Einstein "If you think you're a big fish, it's probably because you only swim in small ponds." ~TheXman
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now