Re: AHKhttp - HTTP Server
Posted: 20 May 2017, 23:50
badpost
Let's help each other out
https://www.autohotkey.com/boards/
What are the steps to make it live on the internet? Only set the port in AHKsock and run the script?metacognition wrote:yes.
Code: Select all
#Persistent
#SingleInstance, force
SetBatchLines, -1
paths := {}
paths["/"] := Func("HelloWorld")
paths["/ajax"] := Func("handleAjax")
paths["/javascript"] := Func("javascript")
paths["/css"] := Func("css")
paths["404"] := Func("NotFound")
paths["/logo"] := Func("Logo")
server := new HttpServer()
server.LoadMimes(A_ScriptDir . "/mime.types")
server.SetPaths(paths)
server.Serve(8000)
return
Logo(ByRef req, ByRef res, ByRef server) {
server.ServeFile(res, A_ScriptDir . "/logo.png")
res.status := 200
}
NotFound(ByRef req, ByRef res) {
res.SetBodyText("Page not found")
}
javascript(ByRef req, ByRef res) {
js := getJS()
res.SetBodyText(js)
res.status := 200
}
css(ByRef req, ByRef res, server) {
css := getCSS()
res.headers["Content-Type"] := "text/css"
res.SetBodyText(css)
res.status := 200
}
handleAjax(ByRef req, ByRef res, server) {
if (func := req.queries.func) {
if (req.queries.params)
params := StrSplit(req.queries.params, "|")
retval := %func%(params ? params : [])
}
res.status := 200
res.SetBodyText(retval)
}
write(params) {
FileRead, repos, autohotkey_repositories.json
FileDelete, autohotkey_repositories.json
if (!repos)
repos := {repos: []}
else
repos := Jxon_Load(repos)
for index, item in params
repos.repos.push(item)
FileAppend, % Jxon_Dump(repos), autohotkey_repositories.json
return Jxon_Dump(repos)
}
HelloWorld(ByRef req, ByRef res) {
html := getHTML()
res.SetBodyText(html)
res.status := 200
}
#include, %A_ScriptDir%\AHKhttp.ahk
#include <AHKsock>
#Include <Jxon>
getHTML() {
html :=
( LTrim Join
"<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Mithril and AHK, Wow!</title>
<script src='//unpkg.com/mithril/mithril.js'></script>
<link href='/css' rel='stylesheet' />
</head>
<body></body>
<script src='/javascript'></script>
</html>"
)
return html
}
getJS() {
JS =
( LTrim Join`n
var root = document.body;
var handleClick = function(text) {
window.open(text);
};
var getRepos = function(page) {
m.request({
method: "GET",
url: "https://api.github.com/search/repositories?q=language:AutoHotkey&page=" + page
}).then(function(data) {
reposList.list = data.items;
});
};
var reposList = {
list: [],
oninit: function(vnode) {
vnode.state.page = vnode.attrs.page;
m.request({
method: "GET",
url: "https://api.github.com/search/repositories?q=language:AutoHotkey&page=" + vnode.state.page
}).then(function(data) {
reposList.list = data.items;
});
},
view: function(vnode) {
return m("ul.list", reposList.list.map(function(repo) {
return m("li.list-item",
m("span", {
onclick: function() {handleClick(repo.html_url)},
title: repo.description
}, repo.full_name)
`)
})
`)
}
};
var navButtons = {
view: function (vnode) {
return [
m("button", {onclick: function() {if (nav.currPage === 1) return; nav.currPage--; getRepos(nav.currPage)}}, "Previous"),
m("span.currPage", vnode.attrs.page),
m("button", {onclick: function() {nav.currPage++, getRepos(nav.currPage)}}, "Next")
];
}
};
var nav = {
currPage: 1,
view: function(vnode) {
return m(".container", [
m(".nav", m(navButtons, {page: vnode.state.currPage})),
m(reposList, {page: vnode.state.currPage})
]);
}
};
m.mount(root, nav);
)
return js
}
getCSS() {
CSS =
( LTrim Join`n
.list {list-style:none;margin:0 0 10px;padding:0;display:flex;align-items:center;align-content:flex-end;justify-content:center;flex-direction:row;flex-wrap:wrap;flex-flow:wrap;}
.list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;width: 20`%}
.list-item > span:hover {text-decoration:underline;cursor: pointer;}
.nav {padding:5px;margin-bottom:20px;border: 1px solid #ddd;border-radius:5px;display: flex;align-items:center;justify-content:center;}
.currPage {margin: 1`%;}
)
return CSS
}
wywywywy wrote: ↑13 Jan 2019, 05:55Hi,
There's a memory leak somewhere, even when using the example scripts.
After receiving some amount of requests (1000? 2000?) it will crash.
I am not sure if the problem is in AhkHttp.ahk or AhkSock.ahk.
I guess maybe an object or array is not cleared somewhere? I have had a look but couldn't find anything.
Has anyone any idea please?
Thanks.
Code: Select all
class Uri
{
Decode(str) {
Loop
If RegExMatch(str, "i)(?<=%)[\da-f]{1,2}", hex)
StringReplace, str, str, `%%hex%, % Chr("0x" . hex), All
Else Break
Return, str
}
Encode(str) {
f = %A_FormatInteger%
SetFormat, Integer, Hex
If RegExMatch(str, "^\w+:/{0,2}", pr)
StringTrimLeft, str, str, StrLen(pr)
StringReplace, str, str, `%, `%25, All
Loop
If RegExMatch(str, "i)[^\w\.~%]", char)
StringReplace, str, str, %char%, % "%" . Asc(char), All
Else Break
SetFormat, Integer, %f%
Return, pr . str
}
}
class HttpServer
{
static servers := {}
LoadMimes(file) {
if (!FileExist(file))
return false
FileRead, data, % file
types := StrSplit(data, "`n")
this.mimes := {}
for i, data in types {
info := StrSplit(data, " ")
type := info.Remove(1)
; Seperates type of content and file types
info := StrSplit(LTrim(SubStr(data, StrLen(type) + 1)), " ")
for i, ext in info {
this.mimes[ext] := type
}
}
return true
}
GetMimeType(file) {
default := "text/plain"
if (!this.mimes)
return default
SplitPath, file,,, ext
type := this.mimes[ext]
if (!type)
return default
return type
}
ServeFile(ByRef response, file) {
f := FileOpen(file, "r")
length := f.RawRead(data, f.Length)
f.Close()
response.SetBody(data, length)
res.headers["Content-Type"] := this.GetMimeType(file)
}
SetPaths(paths) {
this.paths := paths
}
Handle(ByRef request) {
response := new HttpResponse()
if (!this.paths[request.path]) {
func := this.paths["404"]
response.status := 404
if (func)
func.(request, response, this)
return response
} else {
this.paths[request.path].(request, response, this)
}
return response
}
Serve(port) {
this.port := port
HttpServer.servers[port] := this
AHKsock_Listen(port, "HttpHandler")
}
}
HttpHandler(sEvent, iSocket = 0, sName = 0, sAddr = 0, sPort = 0, ByRef bData = 0, bDataLength = 0) {
static sockets := {}
if (!sockets[iSocket]) {
sockets[iSocket] := new Socket(iSocket)
AHKsock_SockOpt(iSocket, "SO_KEEPALIVE", true)
}
socket := sockets[iSocket]
if (sEvent == "DISCONNECTED") {
socket.request := false
sockets[iSocket] := false
} else if (sEvent == "SEND") {
if (socket.TrySend()) {
socket.Close()
}
} else if (sEvent == "RECEIVED") {
server := HttpServer.servers[sPort]
text := StrGet(&bData, "UTF-8")
; New request or old?
if (socket.request) {
; Get data and append it to the existing request body
socket.request.bytesLeft -= StrLen(text)
socket.request.body := socket.request.body . text
request := socket.request
} else {
; Parse new request
request := new HttpRequest(text)
length := request.headers["Content-Length"]
request.bytesLeft := length + 0
if (request.body) {
request.bytesLeft -= StrLen(request.body)
}
}
if (request.bytesLeft <= 0) {
request.done := true
} else {
socket.request := request
}
if (request.done || request.IsMultipart()) {
response := server.Handle(request)
if (response.status) {
socket.SetData(response.Generate())
}
}
if (socket.TrySend()) {
if (!request.IsMultipart() || request.done) {
socket.Close()
}
}
}
}
class HttpRequest
{
__New(data = "") {
if (data)
this.Parse(data)
}
GetPathInfo(top) {
results := []
while (pos := InStr(top, " ")) {
results.Insert(SubStr(top, 1, pos - 1))
top := SubStr(top, pos + 1)
}
this.method := results[1]
this.path := Uri.Decode(results[2])
this.protocol := top
}
GetQuery() {
pos := InStr(this.path, "?")
query := StrSplit(SubStr(this.path, pos + 1), "&")
if (pos)
this.path := SubStr(this.path, 1, pos - 1)
this.queries := {}
for i, value in query {
pos := InStr(value, "=")
key := SubStr(value, 1, pos - 1)
val := SubStr(value, pos + 1)
this.queries[key] := val
}
}
Parse(data) {
this.raw := data
data := StrSplit(data, "`n`r")
headers := StrSplit(data[1], "`n")
this.body := LTrim(data[2], "`n")
this.GetPathInfo(headers.Remove(1))
this.GetQuery()
this.headers := {}
for i, line in headers {
pos := InStr(line, ":")
key := SubStr(line, 1, pos - 1)
val := Trim(SubStr(line, pos + 1), "`n`r ")
this.headers[key] := val
}
}
IsMultipart() {
length := this.headers["Content-Length"]
expect := this.headers["Expect"]
if (expect = "100-continue" && length > 0)
return true
return false
}
}
class HttpResponse
{
__New() {
this.headers := {}
this.status := 0
this.protocol := "HTTP/1.1"
this.buffer:=""
this.SetBodyText("")
}
__Delete(){
this.buffer:=""
}
Generate() {
FormatTime, date,, ddd, d MMM yyyy HH:mm:ss
this.headers["Date"] := date
headers := this.protocol . " " . this.status . "`r`n"
for key, value in this.headers {
headers := headers . key . ": " . value . "`r`n"
}
headers := headers . "`r`n"
length := this.headers["Content-Length"]
this.buffer := new Buffer((StrLen(headers) * 2) + length)
this.buffer.WriteStr(headers)
this.buffer.Append(this.body)
this.buffer.Done()
return this.buffer
}
SetBody(ByRef body, length) {
this.body := new Buffer(length)
this.body.Write(&body, length)
this.headers["Content-Length"] := length
}
SetBodyText(text) {
this.body := Buffer.FromString(text)
this.headers["Content-Length"] := this.body.length
}
}
class Socket
{
__New(socket) {
this.socket := socket
}
Close(timeout = 5000) {
AHKsock_Close(this.socket, timeout)
}
SetData(data) {
this.data := data
}
TrySend() {
if (!this.data || this.data == "")
return false
p := this.data.GetPointer()
length := this.data.length
this.dataSent := 0
loop {
if ((i := AHKsock_Send(this.socket, p, length - this.dataSent)) < 0) {
if (i == -2) {
return
} else {
; Failed to send
return
}
}
if (i < length - this.dataSent) {
this.dataSent += i
} else {
break
}
}
this.dataSent := 0
this.data := ""
return true
}
}
class Buffer
{
__New(len) {
this.SetCapacity("buffer", len)
this.length := 0
}
__Delete(){
this.buffer:=""
}
FromString(str, encoding = "UTF-8") {
length := Buffer.GetStrSize(str, encoding)
this.buffer := new Buffer(length)
this.buffer.WriteStr(str)
return this.buffer
}
GetStrSize(str, encoding = "UTF-8") {
encodingSize := ((encoding="utf-16" || encoding="cp1200") ? 2 : 1)
; length of string, minus null char
return StrPut(str, encoding) * encodingSize - encodingSize
}
WriteStr(str, encoding = "UTF-8") {
length := this.GetStrSize(str, encoding)
VarSetCapacity(text, length)
StrPut(str, &text, encoding)
this.Write(&text, length)
return length
}
; data is a pointer to the data
Write(data, length) {
p := this.GetPointer()
DllCall("RtlMoveMemory", "uint", p + this.length, "uint", data, "uint", length)
this.length += length
}
Append(ByRef buffer) {
destP := this.GetPointer()
sourceP := buffer.GetPointer()
DllCall("RtlMoveMemory", "uint", destP + this.length, "uint", sourceP, "uint", buffer.length)
this.length += buffer.length
}
GetPointer() {
return this.GetAddress("buffer")
}
Done() {
this.SetCapacity("buffer", this.length)
}
}
It's same to the first topic.Just fix memory leak.
Code: Select all
#Persistent
#SingleInstance, force
SetBatchLines, -1
paths := {}
paths["/"] := Func("HelloWorld")
paths["404"] := Func("NotFound")
paths["/logo"] := Func("Logo")
server := new HttpServer()
server.LoadMimes(A_ScriptDir . "/mime.types")
server.SetPaths(paths)
server.Serve(8000)
return
Logo(ByRef req, ByRef res, ByRef server) {
server.ServeFile(res, A_ScriptDir . "/logo.png")
res.status := 200
}
NotFound(ByRef req, ByRef res) {
res.SetBodyText("Page not found")
}
HelloWorld(ByRef req, ByRef res) {
res.SetBodyText("Hello World")
res.status := 200
}
#include,AHKhttp.ahk
#include,ahksock-unicode.ahk
what is it? Is there a special unicode version? I cannot find another reference to this on this forum#include,ahksock-unicode.ahk
What's your testing script?CrashKoeck wrote: ↑08 Nov 2020, 18:28
At about 3,000 calls during stress testing I do notice everything tends to slow down quite a bit and at around 10,000 calls I get the error Function recursion limit exceeded (attached pic)
Something I threw together quick that allows me to adjust the parameters for each test. I hit the ~10K limit whether I let it just run wild with no delays built in or add a couple second delay between each call.magusneo wrote: ↑16 Nov 2020, 07:21What's your testing script?CrashKoeck wrote: ↑08 Nov 2020, 18:28
At about 3,000 calls during stress testing I do notice everything tends to slow down quite a bit and at around 10,000 calls I get the error Function recursion limit exceeded (attached pic)
Code: Select all
#NoEnv
#SingleInstance, Off
global theURL
global targetcalls := 50000
global callCount := 0
global Prog := 0
global CallTimeAvgTally := 0
global CallTimeAvg := 0
global ServerSleep := 0
global SleepCount := 0
global SleepRestTrigger := 250
global ServerRestSeconds := 30
global ServerRestms := ServerRestSeconds * 1000
Progress, x100 y100 w750 m cbe6820a cw222222 cte6820a c01, Waiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input...`nWaiting for user input..., Calls: 0/0, Stress Tester (Press Esc to quit)
Progress, 100
InputBox, targetcalls , Server Stress Tester, Target successful calls,,300,125,,, Locale,, %targetcalls%
if ErrorLevel
ExitApp
InputBox, theURL , Server Stress Tester, Select URL to stress test,,300,125,,, Locale,,
if ErrorLevel
ExitApp
InputBox, ServerSleep , Server Stress Tester, Delay between calls (ms),,300,125,,, Locale,, %ServerSleep%
if ErrorLevel
ExitApp
SleepRestTrigger += ServerSleep
InputBox, SleepRestTrigger , Server Stress Tester, Sleep threshold (ms),,300,125,,, Locale,, %SleepRestTrigger%
if ErrorLevel
ExitApp
InputBox, ServerRestSeconds , Server Stress Tester, Sleep duration (s),,300,125,,, Locale,, %ServerRestSeconds%
if ErrorLevel
ExitApp
Progress, 0, Connecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server...`nConnecting to server..., Calls: 0/%targetcalls%, Stress Tester (Press Esc to quit)
global targetcallsadjusted := 100/targetcalls
Stress()
Return
Stress(){
TestStart := A_TickCount
Try {
tester := ComObjCreate("WinHttp.WinHttpRequest.5.1")
CallTime := A_TickCount
Loop
{
callCount := A_Index
tester.Open("GET",theURL)
tester.Send()
output := tester.ResponseText
outputstatus := tester.Status
Prog += targetcallsadjusted
x := A_TickCount - CallTime - ServerSleep
if(x > SleepRestTrigger and callCount > 1){
If(SleepCount > 10){
MsgBox, 4096, Server Rest Exceeded, Server was allowed to rest for %ServerRestSeconds% seconds 10 times and still exceeded %SleepRestTrigger%ms.`nURL: %theURL%`nReturned Data: %output%`nStatus: %outputstatus%`nTarget Calls: %targetcalls%`nCurrent Call Time: %x%ms`nAvg Call Time: %CallTimeAvg%`nCall Delay: %ServerSleep%`nSleep Cycles: %SleepCount%`nSleep Threshold: %SleepRestTrigger%ms`nSleep Duration: %ServerRestSeconds%s, Calls: %callCount%
ExitApp
} else {
Progress, %Prog%, URL: %theURL%`nReturned Data: %output%`nStatus: %outputstatus%`nCurrent Call Time: %x%ms`nAvg Call Time: %CallTimeAvg%`nCall Delay: %ServerSleep%`nSleep Cycles: Sleeping...`nSleep Threshold: %SleepRestTrigger%ms`nSleep Duration: %ServerRestSeconds%s, Calls: %callCount%/%targetcalls%, Stress Tester (Press Esc to quit)
Sleep, % ServerRestms
SleepCount++
}
}
CallTime := A_TickCount
CallTimeAvgTally += x
if(A_Index < 100){
CallTimeAvg := "Calculating..."
} else {
CallTimeAvg := Round(CallTimeAvgTally/callCount) . "ms"
}
Progress, %Prog%, URL: %theURL%`nReturned Data: %output%`nStatus: %outputstatus%`nCurrent Call Time: %x%ms`nAvg Call Time: %CallTimeAvg%`nCall Delay: %ServerSleep%`nSleep Cycles: %SleepCount%`nSleep Threshold: %SleepRestTrigger%ms`nSleep Duration: %ServerRestSeconds%s, Calls: %callCount%/%targetcalls%, Stress Tester (Press Esc to quit)
if(Prog >= 100){
TestDuration := (A_TickCount - TestStart) / 1000
MsgBox,4096,, Sucessfully made %callCount% calls in %TestDuration% seconds.
ExitApp
}
Sleep, % ServerSleep
}
} catch e {
MsgBox,4096, ERROR, % callCount . " calls were made before there was an error`n`nWhat: " . e.what . "`n-----`nFile: " . e.file . "`n-----`nLine: " . e.line . "`n-----`nMessage: " . e.message . "`n-----`nExtra: " . e.extra . "`n-----`nRAW: " . e
MsgBox,4096, ERROR, URL: %theURL%`nReturned Data: %output%`nStatus: %outputstatus%`nTarget Calls: %targetcalls%`nCalls: %callCount%`nCurrent Call Time: %x%ms`nAvg Call Time: %CallTimeAvg%`nCall Delay: %ServerSleep%`nSleep Cycles: %SleepCount%`nSleep Threshold: %SleepRestTrigger%ms`nSleep Duration: %ServerRestSeconds%s
ExitApp
}
}
Esc::
ExitApp
return