From b48ece9d408aaec6ce0c40116f8d8e70734837be Mon Sep 17 00:00:00 2001 From: cblgh Date: Thu, 22 Oct 2026 16:08:21 +0200 Subject: [PATCH 1/5] examples: add http-client using net/http:Client --- .gitignore | 2 + examples/README.md | 2 + examples/http-dialer/README.md | 45 ++++ examples/http-dialer/client/http-client.go | 268 +++++++++++++++++++++ examples/http-dialer/go.mod | 9 + examples/http-dialer/go.sum | 2 + examples/http-dialer/http-dialer.go | 264 ++++++++++++++++++++ examples/http-dialer/makefile | 2 + 8 files changed, 594 insertions(+) create mode 100644 examples/http-dialer/README.md create mode 100644 examples/http-dialer/client/http-client.go create mode 100644 examples/http-dialer/go.mod create mode 100644 examples/http-dialer/go.sum create mode 100644 examples/http-dialer/http-dialer.go create mode 100644 examples/http-dialer/makefile diff --git a/.gitignore b/.gitignore index c6b3eb0..8cd3abd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,7 @@ examples/echosrv/echosrv examples/echosrv/dumbpipe examples/echo-endpoint/echo-endpoint examples/echo-endpoint/dumbpipe +examples/http-dialer/http-dialer +examples/http-dialer/http-client examples/timeserve/timeserve iroh-go/target/ diff --git a/examples/README.md b/examples/README.md index 8bd6e38..54a50c4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -22,6 +22,8 @@ that leverages iroh's functionality. that is served only over iroh. * [`echo-endpoint`](./echo-endpoint) sets up a HTTP echo server that can gate its HTTP routes based on the Iroh public key making requests. +* [`http-dialer`](./http-dialer) shows how to send a POST request over iroh using the `net/http` + standard library approach diff --git a/examples/http-dialer/README.md b/examples/http-dialer/README.md new file mode 100644 index 0000000..8714cb1 --- /dev/null +++ b/examples/http-dialer/README.md @@ -0,0 +1,45 @@ +# http-dialer + +Demonstrates an http client in go that dials an http server, also in go, over Iroh using +from the standard library `net/http` (e.g. `http.Client`) + +This example builds on `echo-endpoint` and does away with any external program dependencies; +everything is done in go. + +## Build a binary + +``` +go build --ldflags '-linkmode external -extldflags=-static -s -w' ./http-dialer.go +``` + +If you run into `glibc` incompatibility, you can compile with `zig`/`musl`. + +``` +CC="zig cc -target x86_64-linux-musl -lunwind" \ +CGO_ENABLED=1 \ +CGO_LDFLAGS="-static" \ +GOOS=linux GOARCH=amd64 \ +go build -v -a -ldflags '-extldflags "-static"' ./http-dialer.go +``` + +## Terminal 1 (http server) + +``` +./http-dialer +// outputs, among other things, an endpoint ticket you need to provide in terminal 2 +``` + +## Terminal 2 (http client) + +We send a HTTP POST request to our http server over iroh by using the program `http-client` +from `client/` subdirectory of this example: + +``` +./client/http-client --address hello hello +``` + +You should see the string "hello hello" echoed back out to you, prefixed with the +public key of the iroh endpoint establishing the connection in Terminal 1. + +If you run the program again, you will see another prefixed public key. This is because +`http-client` creates a new Iroh identity each time it runs. diff --git a/examples/http-dialer/client/http-client.go b/examples/http-dialer/client/http-client.go new file mode 100644 index 0000000..f8c2c1c --- /dev/null +++ b/examples/http-dialer/client/http-client.go @@ -0,0 +1,268 @@ +package main + +import ( + "errors" + "bytes" + "strings" + "time" + "flag" + "fmt" + "io" + "net" + "net/http" + "os" + + "gomod.cblgh.org/cerca/util/eout" + + iroh "git.coopcloud.tech/decentral1se/iroh-go" +) + +var ( + alpn = []byte("iroh-go-http-dialer/0") +) + +type SimplePipe struct { + recv *iroh.RecvStream + send *iroh.SendStream + totalBytesRead int +} + +func (sp *SimplePipe) setRecv(recv *iroh.RecvStream) { + sp.recv = recv +} + +func (sp *SimplePipe) setSend(send *iroh.SendStream) { + sp.send = send +} + +func (sp *SimplePipe) Read(buf []byte) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + retbuf, err := sp.recv.Read(uint32(len(buf))) + + // signal end of file + if sp.totalBytesRead > 0 && len(retbuf) == 0 { + return 0, io.EOF + } + + sp.totalBytesRead += len(retbuf) + + copyBytes := func() { + for i, d := range retbuf { + buf[i] = d + } + } + + if err != nil { + copyBytes() + return len(retbuf), err + } + copyBytes() + return len(retbuf), err +} + +func (sp *SimplePipe) Write(buf []byte) (int, error) { + n64, err := sp.send.Write(buf) + n := int(n64) + + if n < len(buf) { + return n, errors.New("SimplePipe.Write exited before writing all bytes") + } + + return n, err +} + +var done = make(chan os.Signal, 1) + +var verbose bool +var httpServerAddress string +var additionalInput string + +func log(s string) { + if verbose { + fmt.Println(s) + } +} + +type IrohConn struct { + sp *SimplePipe + addrLocal *IrohAddr + addrRemote *IrohAddr +} + +type IrohAddr struct { + internalAddr string +} +func (addr *IrohAddr) String() string { + return addr.internalAddr +} +func (addr *IrohAddr) Network() string { + return "tcp" +} + +func newIrohConn(remoteEndpoint, localEndpoint string) *IrohConn { + ic := IrohConn{ + sp: &SimplePipe{}, + addrLocal: &IrohAddr{localEndpoint}, + addrRemote: &IrohAddr{remoteEndpoint}, + } + return &ic +} + +// Read reads data from the connection. +// Read can be made to time out and return an error after a fixed +// time limit; see SetDeadline and SetReadDeadline. +func (ic *IrohConn) Read(b []byte) (n int, err error) { + return ic.sp.Read(b) +} + +// Write writes data to the connection. +// Write can be made to time out and return an error after a fixed +// time limit; see SetDeadline and SetWriteDeadline. +func (ic *IrohConn) Write(b []byte) (n int, err error) { + return ic.sp.Write(b) +} + +// Close closes the connection. +// Any blocked Read or Write operations will be unblocked and return errors. +// Close may or may not block until any buffered data is sent; +// for TCP connections see [*TCPConn.SetLinger]. +func (ic *IrohConn) Close() error { + return fmt.Errorf("TODO Close()") +} + +// LocalAddr returns the local network address, if known. +func (ic *IrohConn) LocalAddr() net.Addr { + return ic.addrLocal +} + +// RemoteAddr returns the remote network address, if known. +func (ic *IrohConn) RemoteAddr() net.Addr { + return ic.addrRemote +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail instead of blocking. The deadline applies to all future +// and pending I/O, not just the immediately following call to +// Read or Write. After a deadline has been exceeded, the +// connection can be refreshed by setting a deadline in the future. +// +// If the deadline is exceeded a call to Read or Write or to other +// I/O methods will return an error that wraps os.ErrDeadlineExceeded. +// This can be tested using errors.Is(err, os.ErrDeadlineExceeded). +// The error's Timeout method will return true, but note that there +// are other possible errors for which the Timeout method will +// return true even if the deadline has not been exceeded. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful Read or Write calls. +// +// A zero value for t means I/O operations will not time out. +func (ic *IrohConn) SetDeadline(t time.Time) error { + return fmt.Errorf("TODO SetDeadline") +} + +// SetReadDeadline sets the deadline for future Read calls +// and any currently-blocked Read call. +// A zero value for t means Read will not time out. +func (ic *IrohConn) SetReadDeadline(t time.Time) error { + return fmt.Errorf("TODO SetReadDeadline") +} + +// SetWriteDeadline sets the deadline for future Write calls +// and any currently-blocked Write call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means Write will not time out. +func (ic *IrohConn) SetWriteDeadline(t time.Time) error { + return fmt.Errorf("TODO SetWriteDeadline") +} + +// Receives addressing information and sets up an iroh connection to the endpoint described by the addressing +// information, and returns the receive/send streams in a format that golang's net.http ecosystem will accept. +func DialIroh(network, address string) (net.Conn, error) { + // TODO (2026-10-22): can be improved by taking an endpoint or a ticket + log(fmt.Sprintf("DialIroh: network %q address %q\n", network, address)) + remoteEndpointString := strings.Split(address, ":")[0] + var localEndpointString string + log(fmt.Sprintf("DialIroh: endpoint id %q ", remoteEndpointString)) + remote, err := iroh.EndpointIdFromString(remoteEndpointString) + if err := iroh.As(err); err != nil { + panic(err) + } + addr := iroh.NewEndpointAddr(remote, nil, nil) + + preset := iroh.PresetN0() + opts := iroh.EndpointOptions{ + Preset: &preset, + Alpns: &[][]byte{alpn}, + } + + e, err := iroh.EndpointBind(opts) + if err := iroh.As(err); err != nil { + panic(err) + } + localEndpointString = e.Id().String() + + // NOTE (2026-10-22): pretty sure program execution stops here if you can't + // get a connection to a relay (e.g. if you are rate-limited for too many tries) + e.Online() + + log(fmt.Sprintf("online as %s\n", localEndpointString)) + + conn, err := e.Connect(addr, alpn) + if err := iroh.As(err); err != nil { + panic(err) + } + + stream, err := conn.OpenBi() + if err := iroh.As(err); err != nil { + panic(err) + } + + irohConn := newIrohConn(remoteEndpointString, localEndpointString) + + irohConn.sp.setRecv(stream.Recv()) + irohConn.sp.setSend(stream.Send()) + + return irohConn, nil +} + +func parseFlags() { + flag.BoolVar(&verbose, "v", false, "output more log messages") + flag.StringVar(&httpServerAddress, "address", "", "iroh endpoint serving an http server") + flag.Parse() + additionalInput = strings.Join(flag.Args(), " ") +} + +func main() { + parseFlags() + if httpServerAddress == "" { + fmt.Println("must provide --address") + os.Exit(1) + } + // setup our dialer + transport := http.Transport{ + Dial: DialIroh, + } + client := &http.Client{ + Transport: &transport, + } + createUrl := func (endpoint, urlpath string) string { + return fmt.Sprintf("http://%s%s", endpoint, urlpath) + } + // try to dial and send a POST request + resp, err := client.Post(createUrl(httpServerAddress, "/"), "text/plain", bytes.NewBufferString(additionalInput)) + log(fmt.Sprintf("%s", resp)) + eout.Check(err, "client.Post") + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + eout.Check(err, "read body") + fmt.Println(string(body)) +} diff --git a/examples/http-dialer/go.mod b/examples/http-dialer/go.mod new file mode 100644 index 0000000..25d0d58 --- /dev/null +++ b/examples/http-dialer/go.mod @@ -0,0 +1,9 @@ +module git.coopcloud.tech/decentral1se/iroh-go/examples/http-dialer + +go 1.26.1 + +require git.coopcloud.tech/decentral1se/iroh-go v0.0.0-20260717110820-68ad06fe2a25 + +require gomod.cblgh.org/cerca v0.3.3 // indirect + +replace git.coopcloud.tech/decentral1se/iroh-go => ../../ diff --git a/examples/http-dialer/go.sum b/examples/http-dialer/go.sum new file mode 100644 index 0000000..a350675 --- /dev/null +++ b/examples/http-dialer/go.sum @@ -0,0 +1,2 @@ +gomod.cblgh.org/cerca v0.3.3 h1:NaDO+J/cHn5nIsmLhjBUfWb/4RkNg6Dn3frrbX7TvHc= +gomod.cblgh.org/cerca v0.3.3/go.mod h1:ZLFyHtI0Dd9GohYRn2Y+HpY3vpHQHa21efOTeSXmWvo= diff --git a/examples/http-dialer/http-dialer.go b/examples/http-dialer/http-dialer.go new file mode 100644 index 0000000..20b8dbd --- /dev/null +++ b/examples/http-dialer/http-dialer.go @@ -0,0 +1,264 @@ +package main + +import ( + "context" + "crypto/sha256" + "errors" + "flag" + "fmt" + "io" + "net" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + + "gomod.cblgh.org/cerca/util/eout" + + iroh "git.coopcloud.tech/decentral1se/iroh-go" +) + +var ( + alpn = []byte("iroh-go-http-dialer/0") + conns sync.Map +) + +type SimplePipe struct { + recv *iroh.RecvStream + send *iroh.SendStream + totalBytesRead int +} + +func (sp *SimplePipe) setRecv(recv *iroh.RecvStream) { + sp.recv = recv +} + +func (sp *SimplePipe) setSend(send *iroh.SendStream) { + sp.send = send +} + +func (sp *SimplePipe) Read(buf []byte) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + retbuf, err := sp.recv.Read(uint32(len(buf))) + + // signal end of file + if sp.totalBytesRead > 0 && len(retbuf) == 0 { + return 0, io.EOF + } + + sp.totalBytesRead += len(retbuf) + + copyBytes := func() { + for i, d := range retbuf { + buf[i] = d + } + } + + if err != nil { + copyBytes() + return len(retbuf), err + } + copyBytes() + return len(retbuf), err +} + +func (sp *SimplePipe) Write(buf []byte) (int, error) { + n64, err := sp.send.Write(buf) + n := int(n64) + + if n < len(buf) { + return n, errors.New("SimplePipe.Write exited before writing all bytes") + } + + return n, err +} + +var done = make(chan os.Signal, 1) + +func awaitInterrupt() { + signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) + <-done + os.Exit(0) +} + +var PORT int +var verbose bool + +func log(s string) { + if verbose { + fmt.Println(s) + } +} + +type ITCP struct { + LocalListener net.Listener + Endpoint *iroh.Endpoint + ch chan string +} + +// create an iroh endpoint +func newITCP() *ITCP { + var t ITCP + t.ch = make(chan string) + + preset := iroh.PresetN0() + opts := iroh.EndpointOptions{ + Preset: &preset, + Alpns: &[][]byte{alpn}, + } + + // NOTE (2026-10-19): static endpoint key being set + digest := sha256.Sum256([]byte("http-dialer")) + secret := digest[:] + opts.SecretKey = &secret + + endpoint, err := iroh.EndpointBind(opts) + if err := iroh.As(err); err != nil { + panic(err) + } + + endpoint.Online() + fmt.Printf("online as %s\n", endpoint.Id()) + + ticket, err := iroh.EndpointTicketFromAddr(endpoint.Addr()) + eout.Check(err, "format ticket") + fmt.Println("\nticket:") + fmt.Println(ticket, "\n") + + t.Endpoint = endpoint + return &t +} + +func getHost() string { + return fmt.Sprintf(":%d", PORT) +} + +func (t *ITCP) Start(ctx context.Context) { + tcphost := getHost() + var err error + log(fmt.Sprintf("setting up local listener on %s", tcphost)) + t.LocalListener, err = net.Listen("tcp", tcphost) + eout.Check(err, "set up local listener") + + go t.handleIncoming(tcphost) +} + +// implements net.Listener +func (t *ITCP) Accept() (net.Conn, error) { + c, err := t.LocalListener.Accept() + // receive endpoint id from t.handleIncoming goroutine + endpointId := <-t.ch + conns.Store(c, endpointId) + return c, err +} + +// implements net.Listener +func (t *ITCP) Close() error { + // TODO (2026-10-21) + return t.LocalListener.Close() +} + +// implements net.Listener +func (t *ITCP) Addr() net.Addr { + return t.LocalListener.Addr() +} + +func (t *ITCP) handleIncoming(tcphost string) { + for { + incoming := t.Endpoint.AcceptNext() + log("incoming") + accepting, err := (*incoming).Accept() + if err := iroh.As(err); err != nil { + eout.Check(err, "incoming.Accept") + } + log("accepted") + conn, err := accepting.Connect() + if err := iroh.As(err); err != nil { + eout.Check(err, "incoming.Connect") + } + log("connected") + stream, err := conn.AcceptBi() + if err := iroh.As(err); err != nil { + eout.Check(err, "incoming.AcceptBi") + panic(err) + } + log("bidi stream") + + // send endpoint public key to be received by accept handler + go func(endpointId string) { + t.ch <- endpointId + }(conn.RemoteId().String()) + + recv := stream.Recv() + send := stream.Send() + + var sp SimplePipe + sp.setRecv(recv) + sp.setSend(send) + + log("pre dial") + dialedConn, err := net.Dial("tcp", tcphost) + eout.Check(err, "net.dial") + go io.Copy(dialedConn, &sp) + go io.Copy(&sp, dialedConn) + log("post dial") + } +} + +func parseFlags() { + flag.BoolVar(&verbose, "v", false, "output more log messages") + flag.IntVar(&PORT, "p", 9899, "port for net.Listener to run on") + flag.Parse() +} + +func main() { + parseFlags() + tcpServe := newITCP() + mux := http.NewServeMux() + mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { + log("received req") + body, err := io.ReadAll(req.Body) + // access the endpoint id of the remote sending the request via req.Context set by server.ConnContext + v := req.Context().Value("endpointId") + // return early, endpointId isn't set + if v == nil || v == "" { + log("request did not have endpointId on context") + res.Write(body) + return + } + log(fmt.Sprintf("request context for key 'endpointId' %q", v)) + eout.Check(err, "read body") + log(fmt.Sprintf("data %q\n", string(body))) + res.Write([]byte(fmt.Sprintf("[%s] %s", v, body))) + }) + + server := http.Server{ + Addr: getHost(), + // provide an extra key `endpointId` to all incoming http.Request's such that each handler can access the key + // for a request as `req.Context().Value('endpointId')` + ConnContext: func(ctx context.Context, c net.Conn) context.Context { + var s string + // sync.map 'conns' retrieves incoming net.Conn from map and then deletes it to prevent memory leak + v, ok := conns.LoadAndDelete(c) + if ok { + s = v.(string) + } else { + log("sync.map 'conns' could not retrieve incoming net.Conn from map") + } + // we set the key 'endpointId' on the context, which means we can retrieve it from any handler's request as + // `req.Context().Value('endpointId')` + return context.WithValue(ctx, "endpointId", s) + }, + Handler: mux, + } + + tcpServe.Start(context.Background()) + fmt.Println("started iroh http server") + err := server.Serve(tcpServe) + eout.Check(err, "http.Serve(tcpServe)") + awaitInterrupt() +} diff --git a/examples/http-dialer/makefile b/examples/http-dialer/makefile new file mode 100644 index 0000000..aa931af --- /dev/null +++ b/examples/http-dialer/makefile @@ -0,0 +1,2 @@ +default: + @go build -v ./... -- 2.54.0 From 1954c5fbffebea852cf605b7cd713b03aa008017 Mon Sep 17 00:00:00 2001 From: cblgh Date: Thu, 22 Oct 2026 16:13:57 +0200 Subject: [PATCH 2/5] examples(http-dialer): readme --- examples/http-dialer/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/http-dialer/README.md b/examples/http-dialer/README.md index 8714cb1..eabb7e7 100644 --- a/examples/http-dialer/README.md +++ b/examples/http-dialer/README.md @@ -25,6 +25,7 @@ go build -v -a -ldflags '-extldflags "-static"' ./http-dialer.go ## Terminal 1 (http server) ``` +go build ./http-dialer.go ./http-dialer // outputs, among other things, an endpoint ticket you need to provide in terminal 2 ``` @@ -35,7 +36,8 @@ We send a HTTP POST request to our http server over iroh by using the program `h from `client/` subdirectory of this example: ``` -./client/http-client --address hello hello +go build ./client/http-client.go +./http-client --address hello hello ``` You should see the string "hello hello" echoed back out to you, prefixed with the -- 2.54.0 From 8a35d55e10132c8e7b166e620def238a0cb75a94 Mon Sep 17 00:00:00 2001 From: cblgh Date: Fri, 23 Oct 2026 09:08:00 +0200 Subject: [PATCH 3/5] examples(http-dialer): add note about net.Conn interface --- examples/http-dialer/client/http-client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/http-dialer/client/http-client.go b/examples/http-dialer/client/http-client.go index f8c2c1c..1e157fe 100644 --- a/examples/http-dialer/client/http-client.go +++ b/examples/http-dialer/client/http-client.go @@ -86,6 +86,7 @@ func log(s string) { } } +// IrohConn implements net.Conn (Read,Write,Close + Local/RemoteAddr + Deadline methods) type IrohConn struct { sp *SimplePipe addrLocal *IrohAddr -- 2.54.0 From 5ae7f03db08c6511bdbdef74ebee26f6fd49a7e2 Mon Sep 17 00:00:00 2001 From: cblgh Date: Fri, 23 Oct 2026 09:23:03 +0200 Subject: [PATCH 4/5] examples(http-dialer): cleanup + accept ticket or endpoint as --address --- examples/http-dialer/client/http-client.go | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/examples/http-dialer/client/http-client.go b/examples/http-dialer/client/http-client.go index 1e157fe..5d37a42 100644 --- a/examples/http-dialer/client/http-client.go +++ b/examples/http-dialer/client/http-client.go @@ -74,8 +74,6 @@ func (sp *SimplePipe) Write(buf []byte) (int, error) { return n, err } -var done = make(chan os.Signal, 1) - var verbose bool var httpServerAddress string var additionalInput string @@ -188,16 +186,25 @@ func (ic *IrohConn) SetWriteDeadline(t time.Time) error { // Receives addressing information and sets up an iroh connection to the endpoint described by the addressing // information, and returns the receive/send streams in a format that golang's net.http ecosystem will accept. func DialIroh(network, address string) (net.Conn, error) { - // TODO (2026-10-22): can be improved by taking an endpoint or a ticket log(fmt.Sprintf("DialIroh: network %q address %q\n", network, address)) remoteEndpointString := strings.Split(address, ":")[0] + var localEndpointString string - log(fmt.Sprintf("DialIroh: endpoint id %q ", remoteEndpointString)) - remote, err := iroh.EndpointIdFromString(remoteEndpointString) - if err := iroh.As(err); err != nil { - panic(err) + + var remoteAddr *iroh.EndpointAddr + // first try to parse as ticket + if ticket, err := iroh.EndpointTicketFromString(remoteEndpointString); err == nil { + remoteAddr = ticket.EndpointAddr() + } else { + // fallback to interpret as endpoint id + remote, err := iroh.EndpointIdFromString(remoteEndpointString) + if err := iroh.As(err); err != nil { + panic(err) + } + remoteAddr = iroh.NewEndpointAddr(remote, nil, nil) } - addr := iroh.NewEndpointAddr(remote, nil, nil) + + log(fmt.Sprintf("DialIroh: endpoint id %q ", remoteAddr.Id().String())) preset := iroh.PresetN0() opts := iroh.EndpointOptions{ @@ -217,7 +224,7 @@ func DialIroh(network, address string) (net.Conn, error) { log(fmt.Sprintf("online as %s\n", localEndpointString)) - conn, err := e.Connect(addr, alpn) + conn, err := e.Connect(remoteAddr, alpn) if err := iroh.As(err); err != nil { panic(err) } @@ -260,7 +267,6 @@ func main() { } // try to dial and send a POST request resp, err := client.Post(createUrl(httpServerAddress, "/"), "text/plain", bytes.NewBufferString(additionalInput)) - log(fmt.Sprintf("%s", resp)) eout.Check(err, "client.Post") defer resp.Body.Close() body, err := io.ReadAll(resp.Body) -- 2.54.0 From cdac97b70e4f2ac78177780cf0fb201d1e3b3e23 Mon Sep 17 00:00:00 2001 From: cblgh Date: Fri, 23 Oct 2026 09:27:46 +0200 Subject: [PATCH 5/5] examples(http-dialer): readme edits --- examples/http-dialer/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/http-dialer/README.md b/examples/http-dialer/README.md index eabb7e7..aad9a68 100644 --- a/examples/http-dialer/README.md +++ b/examples/http-dialer/README.md @@ -1,7 +1,7 @@ # http-dialer Demonstrates an http client in go that dials an http server, also in go, over Iroh using -from the standard library `net/http` (e.g. `http.Client`) +the standard library's `http.Client`. This example builds on `echo-endpoint` and does away with any external program dependencies; everything is done in go. @@ -27,21 +27,21 @@ go build -v -a -ldflags '-extldflags "-static"' ./http-dialer.go ``` go build ./http-dialer.go ./http-dialer -// outputs, among other things, an endpoint ticket you need to provide in terminal 2 +// outputs, among other things, an endpoint ticket you need to provide in Terminal 2 ``` ## Terminal 2 (http client) -We send a HTTP POST request to our http server over iroh by using the program `http-client` -from `client/` subdirectory of this example: +Send an HTTP POST request to our http server in Terminal 1 over Iroh using the program `http-client` +from the `client/` sub-directory of this example: ``` go build ./client/http-client.go -./http-client --address hello hello +./http-client --address hello hello ``` You should see the string "hello hello" echoed back out to you, prefixed with the -public key of the iroh endpoint establishing the connection in Terminal 1. +public key of the Iroh endpoint establishing the connection in Terminal 1. If you run the program again, you will see another prefixed public key. This is because `http-client` creates a new Iroh identity each time it runs. -- 2.54.0