Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
laytan committed May 6, 2024
1 parent 642af48 commit 1866c3e
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 43 deletions.
15 changes: 8 additions & 7 deletions client/client.odin
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ Connection_State :: enum {
Failed,
}

// TODO: should response `Set-Cookie` headers automatically be stored on the connection and passed
// along next requests?

Connection :: struct {
allocator: mem.Allocator,

Expand Down Expand Up @@ -106,9 +109,6 @@ Response :: struct {
status: http.Status,
cookies: [dynamic]http.Cookie,
body: http.Body,

// Below is for internal use only:

using _: http.Has_Body,
}

Expand Down Expand Up @@ -181,8 +181,8 @@ connection_init :: proc(conn: ^Connection, client: ^Client, target: string, sche
return true
}

connection_make :: proc(client: ^Client, host: string, scheme := Scheme.From_Target, allocator := context.allocator) -> (conn: Connection) {
connection_init(&conn, client, host, scheme, allocator)
connection_make :: proc(client: ^Client, target: string, scheme := Scheme.From_Target, allocator := context.allocator) -> (conn: Connection) {
connection_init(&conn, client, target, scheme, allocator)
return
}

Expand Down Expand Up @@ -395,9 +395,10 @@ callback_and_process_next :: proc(r: ^Request, err: net.Network_Error) {

log.infof("request done: %v %v", r.res.status, err)

r.on_response(r, r.user_data, err)

r.conn.request = r.next
connection_process(r.conn)
r.on_response(r, r.user_data, err)
}

@(private="file")
Expand Down Expand Up @@ -719,7 +720,7 @@ prepare_request :: proc(r: ^Request) {
// Escape newlines in headers, if we don't, an attacker can find an endpoint
// that returns a header with user input, and inject headers into the response.
esc_value, was_allocation := strings.replace_all(value, "\n", "\\n", r.allocator)
defer if was_allocation do delete(esc_value)
defer if was_allocation do delete(esc_value, r.allocator)

ws(&r.buf, esc_value)
ws(&r.buf, "\r\n")
Expand Down
50 changes: 41 additions & 9 deletions client/dns/dns.odin
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ Client :: struct {
io: ^nbio.IO,

// Hosts/Name servers configuration.
using config: net.DNS_Configuration,
name_servers: []net.Endpoint,
hosts: []net.DNS_Host_Entry,
// init_cb: proc(^Client, rawptr),
// init_ud: rawptr,
// init_state: int,

// Cache.
cache: map[string]Cache_Entry,
Expand All @@ -50,7 +53,7 @@ Callback :: struct {
ud: rawptr,
}

// TODO: provide a callback, OR allow `resolve` before these things are done with some sort of request queue.
// TODO: callback.
init :: proc(c: ^Client, allocator := context.allocator) {
c.allocator = allocator
c.cache.allocator = allocator
Expand All @@ -59,6 +62,33 @@ init :: proc(c: ^Client, allocator := context.allocator) {
load_hosts(c)
}

// Waits until all requests are done and frees all related resources.
destroy :: proc {
destroy_cb,
destroy_no_cb,
}

destroy_no_cb :: proc(c: ^Client) {
destroy_cb(c, nil, proc(_: rawptr) {})
}

destroy_cb :: proc(c: ^Client, user: rawptr, cb: proc(user: rawptr)) {
cache_clear(c)

// Try to clear again next tick, we don't want to interrupt in progress requests.
if len(c.cache) > 0 {
nbio.next_tick(c.io, c, user, cb, destroy_cb)
} else {
delete(c.cache)
delete(c.name_servers, c.allocator)
for h in c.hosts {
delete(h.name, c.allocator)
}
delete(c.hosts, c.allocator)
cb(user)
}
}

// Removes any cache entries that aren't currently being resolved.
cache_clear :: proc(c: ^Client) {
for hostname, entry in c.cache {
Expand Down Expand Up @@ -358,14 +388,15 @@ resolve :: proc(c: ^Client, hostname: string, user: rawptr, cb: On_Resolve) {
// Loads the name servers from the OS, this is called implicitly during `init`.
@(private)
load_name_servers :: proc(c: ^Client) {
if c.resolv_conf == "" {
log.error("empty resolv_conf file path")
resolv_conf := net.DEFAULT_DNS_CONFIGURATION.resolv_conf
if resolv_conf == "" {
log.error("empty resolv_conf file path") // TODO: this is not an error on Windows.
return
}

fd, err := nbio.open(c.io, c.resolv_conf)
fd, err := nbio.open(c.io, resolv_conf)
if err != os.ERROR_NONE {
log.errorf("error opening %q: %v", c.resolv_conf, err)
log.errorf("error opening %q: %v", resolv_conf, err)
return
}

Expand All @@ -388,14 +419,15 @@ load_name_servers :: proc(c: ^Client) {
// Loads the hosts file from the OS, this is implicitly called during `init`.
@(private)
load_hosts :: proc(c: ^Client) {
if c.hosts_file == "" {
hosts_file := net.DEFAULT_DNS_CONFIGURATION.hosts_file
if hosts_file == "" {
log.error("empty hosts file")
return
}

fd, err := nbio.open(c.io, c.hosts_file)
fd, err := nbio.open(c.io, hosts_file)
if err != os.ERROR_NONE {
log.errorf("error opening %q: %v", c.hosts_file, err)
log.errorf("error opening %q: %v", hosts_file, err)
return
}

Expand Down
72 changes: 45 additions & 27 deletions client/test_client.odin
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,53 @@ test_client :: proc(t: ^testing.T) {
nbio.init(&io)
defer nbio.destroy(&io)

client: dns.Client
client.io = &io
client.config = net.dns_configuration // TODO: make default if not set.
dns.init(&client)
// client: dns.Client
// client.io = &io
// client.config = net.dns_configuration // TODO: make default if not set.
// dns.init(&client)
//
// nbio.timeout(client.io, time.Second, &client, proc(client: ^dns.Client, _: Maybe(time.Time)) {
// log.info("Resolving")
//
// dns.resolve(client, "github.com", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("github.com", recs, err)
// })
// dns.resolve(client, "laytanlaats.com", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("laytanlaats.com", recs, err)
// })
// dns.resolve(client, "laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("laytan.dev", recs, err)
// })
// dns.resolve(client, "laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("laytan.dev", recs, err)
// })
// dns.resolve(client, "odin-http.laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("odin-http.laytan.dev", recs, err)
// })
// dns.resolve(client, "odin-http.laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("odin-http.laytan.dev", recs, err)
// })
// dns.resolve(client, "github.com", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
// log.info("github.com", recs, err)
// })
// })

client := client_make(&io)
conn := connection_make(&client, "https://github.com")

nbio.timeout(client.io, time.Second, &client, proc(client: ^dns.Client, _: Maybe(time.Time)) {
log.info("Resolving")
req := request_make(&conn, "/laytan")
request(&req, nil, proc(r: ^Request, _: rawptr, err: net.Network_Error) {
log.infof("%v %v %#v %#v", r.res.status, err, r.res.headers._kv, r.res.cookies)
})

req = request_make(&conn, "/laytan/odin-http")
request(&req, nil, proc(r: ^Request, _: rawptr, err: net.Network_Error) {
log.infof("%v %v %#v %#v", r.res.status, err, r.res.headers._kv, r.res.cookies)
})

dns.resolve(client, "github.com", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("github.com", recs, err)
})
dns.resolve(client, "laytanlaats.com", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("laytanlaats.com", recs, err)
})
dns.resolve(client, "laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("laytan.dev", recs, err)
})
dns.resolve(client, "laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("laytan.dev", recs, err)
})
dns.resolve(client, "odin-http.laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("odin-http.laytan.dev", recs, err)
})
dns.resolve(client, "odin-http.laytan.dev", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("odin-http.laytan.dev", recs, err)
})
dns.resolve(client, "github.com", nil, proc(_: rawptr, recs: dns.Record, err: net.Network_Error) {
log.info("github.com", recs, err)
})
req = request_make(&conn, "/laytan/Odin")
request(&req, nil, proc(r: ^Request, _: rawptr, err: net.Network_Error) {
log.infof("%v %v %#v %#v", r.res.status, err, r.res.headers._kv, r.res.cookies)
})

// log.info(net.resolve("github.com"))
Expand Down
1 change: 1 addition & 0 deletions responses.odin
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ If any other error occurs, a 500 is sent and the error is logged.
*/
respond_file :: proc(r: ^Response, path: string, content_type: Maybe(Mime_Type) = nil, loc := #caller_location) {
// PERF: we are still putting the content into the body buffer, we could stream it.
// PERF: could even use `splice`/`sendfile` on Linux https://tinselcity.github.io/Sendfile-W-Iouring/

assert_has_td(loc)
assert(!r.sent, "response has already been sent", loc)
Expand Down

0 comments on commit 1866c3e

Please sign in to comment.