AHKhttp - HTTP Server

Post your working scripts, libraries and tools
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: AHKhttp - HTTP Server

20 May 2017, 23:50

badpost
Last edited by brutus_skywalker on 27 Sep 2018, 09:44, edited 1 time in total.
Outsourcing Clicks & Presses Since 2004.
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: AHKhttp - HTTP Server

27 May 2017, 01:04

badpost
Last edited by brutus_skywalker on 27 Sep 2018, 09:44, edited 1 time in total.
Outsourcing Clicks & Presses Since 2004.
DanielToward13
Posts: 63
Joined: 18 May 2017, 10:56

Re: AHKhttp - HTTP Server

28 Jul 2017, 22:00

Thanks for sharing.
is it possible to access this HTTP Server using its IP address and port on the internet? is it secure?
Last edited by DanielToward13 on 28 Jul 2017, 22:02, edited 1 time in total.
User avatar
metacognition
Posts: 87
Joined: 22 Oct 2014, 05:57
Location: Alaska

Re: AHKhttp - HTTP Server

28 Jul 2017, 22:02

yes.
DanielToward13
Posts: 63
Joined: 18 May 2017, 10:56

Re: AHKhttp - HTTP Server

28 Jul 2017, 22:07

metacognition wrote:yes.
:thumbup: What are the steps to make it live on the internet? Only set the port in AHKsock and run the script?
User avatar
metacognition
Posts: 87
Joined: 22 Oct 2014, 05:57
Location: Alaska

Re: AHKhttp - HTTP Server

28 Jul 2017, 22:11

If you are using a Nat based router you would have to forward a port, on your router, to that specific port on your pc, using the local ip address, you are headed into deep water here my friend, much deeper than the scope of this forum/topic, I suggest googling about setting up a home http server and port forwarding.
User avatar
kczx3
Posts: 872
Joined: 06 Oct 2015, 21:39

Re: AHKhttp - HTTP Server

25 Aug 2017, 11:33

Started playing around with this a bit and utilizing JS frameworks as I code in JS quite a bit now.

Requires Jxon.ahk as well in a lib folder. Please note, it does write json to the file system in the script's directory. There is likely a better way of handling the js and css.


EDIT: Updated the code some to work with pagination of the Github API and some additional CSS for appeal.

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
Posts: 1
Joined: 12 Jan 2019, 21:15

Re: AHKhttp - HTTP Server

13 Jan 2019, 05:55

Hi,

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.
magusneo
Posts: 36
Joined: 30 Sep 2013, 06:34

Re: AHKhttp - HTTP Server

22 May 2019, 06:02

wywywywy wrote:
13 Jan 2019, 05:55
Hi,

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.

There is memory leak.
Try this

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)
    }
}

User avatar
nnnik
Posts: 4242
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: AHKhttp - HTTP Server

12 Jun 2019, 09:05

Would you mind providing a new example code?
Recommends AHK Studio
magusneo
Posts: 36
Joined: 30 Sep 2013, 06:34

Re: AHKhttp - HTTP Server

28 Jun 2019, 20:30

nnnik wrote:
12 Jun 2019, 09:05
Would you mind providing a new example code?
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

Return to “Scripts and Functions”

Who is online

Users browsing this forum: Ben, Google [Bot] and 43 guests