Example
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, %A_ScriptDir%\AHKhttp.ahk
#include <AHKsock>
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")
request := new HttpRequest(text)
; Multipart request
if (request.IsMultipart()) {
length := request.headers["Content-Length"]
request.bytesLeft := length + 0
if (request.body) {
request.bytesLeft -= StrLen(request.body)
}
socket.request := request
} else if (socket.request) {
; Get data and append it to the request body
socket.request.bytesLeft -= StrLen(text)
socket.request.body := socket.request.body . text
}
if (socket.request) {
request := socket.request
if (request.bytesLeft <= 0) {
request.done := true
}
}
response := server.Handle(request)
if (response.status) {
socket.SetData(response.Generate())
if (socket.TrySend()) {
if (!request.IsMultipart() || (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.SetBodyText("")
}
Generate() {
FormatTime, date,, ddd, d MMM yyyy HH:mm:ss
this.headers["Date"] := date
headers := this.protocol . " " . this.status . "`n"
for key, value in this.headers {
headers := headers . key . ": " . value . "`n"
}
headers := headers . "`n"
length := this.headers["Content-Length"]
buffer := new Buffer((StrLen(headers) * 2) + length)
buffer.WriteStr(headers)
buffer.Append(this.body)
buffer.Done()
return 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
}
FromString(str, encoding = "UTF-8") {
length := Buffer.GetStrSize(str, encoding)
buffer := new Buffer(length)
buffer.WriteStr(str)
return 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)
}
}
Documentation