function listen(f, init_context, host::Union{IPAddr, String} = Sockets.localhost, port::Integer = 1965 ; connection_count::Ref{Int} = Ref(0), readtimeout::Int = 0, verbose::Bool = false) server = Sockets.listen(Sockets.InetAddr(host, port)) verbose && @info "Listening on: $host:$port" return listenloop(f, server, init_context, connection_count, readtimeout, verbose) end """" Main server loop. Accepts new tcp connections and spawns async tasks to handle them." """ function listenloop(f, server, init_context, connection_count, readtimeout, verbose) count = 1 while isopen(server) try verbose && @info "Ready to accept new connection" io = accept(server) if isnothing(io) verbose && @warn "unable to accept new connection" continue end connection_count[] += 1 verbose && @info "$(connection_count[]) total connections" @async try handle_connection(f, server, init_context, io, verbose) catch e if e isa Base.IOError && e.code in (-54, -104) verbose && @warn "connection reset by peer (ECONNRESET)" else @warn exception=(e, stacktrace(catch_backtrace())) end finally connection_count[] -= 1 verbose && @info "$(connection_count[]) total connections" # handle_connection is in charge of closing the underlying io end catch e close(server) if e isa InterruptException @warn "Interrupted: listen($server)" break else rethrow(e) end end count += 1 verbose && @info "Responded to $(count) clients in total" end return end function handle_connection(f, server, init_context, io, verbose) client = SSLClient(init_context(), io) try while isopen(server) && isopen(io) if isreadable(io) && length(client.write_buf) == 0 if OpenSSL.do_sock_read(client) == -1 break end end if iswritable(io) && length(client.write_buf) > 0 verbose && println("do_write") if OpenSSL.do_sock_write(client) == -1 break end end if OpenSSL.ssl_init_finished(client) verbose && println("init_finished") # TODO: add a timeout! while isopen(server) && isopen(io) && (length(client.context.data) < 2 || client.context.data[end-1:end] != UInt8['\r', '\n']) OpenSSL.do_sock_read(client) end f(GeminiRequest(Connection(server, client), Request(String(client.context.data)), Dict())) break end end catch e rethrow(e) finally close(client) end end