Skip to content

Instantly share code, notes, and snippets.

@teknoraver
Last active March 21, 2024 11:48
Show Gist options
  • Save teknoraver/5ffacb8757330715bcbcc90e6d46ac74 to your computer and use it in GitHub Desktop.
Save teknoraver/5ffacb8757330715bcbcc90e6d46ac74 to your computer and use it in GitHub Desktop.
HTTP over Unix domain sockets in golang
package main
import (
"context"
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
)
func main() {
post := flag.String("d", "", "data to POST")
help := flag.Bool("h", false, "usage help")
flag.Parse()
if *help || len(flag.Args()) != 2 {
fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "[-d data] /path.socket /uri")
flag.PrintDefaults()
os.Exit(0)
}
fmt.Println("Unix HTTP client")
httpc := http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", flag.Args()[0])
},
},
}
var response *http.Response
var err error
if len(*post) == 0 {
response, err = httpc.Get("http://unix" + flag.Args()[1])
} else {
response, err = httpc.Post("http://unix"+flag.Args()[1], "application/octet-stream", strings.NewReader(*post))
}
if err != nil {
panic(err)
}
io.Copy(os.Stdout, response.Body)
}
package main
import (
"fmt"
"net"
"net/http"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "/path.sock [wwwroot]")
return
}
fmt.Println("Unix HTTP server")
root := "."
if len(os.Args) > 2 {
root = os.Args[2]
}
os.Remove(os.Args[1])
server := http.Server{
Handler: http.FileServer(http.Dir(root)),
}
unixListener, err := net.Listen("unix", os.Args[1])
if err != nil {
panic(err)
}
server.Serve(unixListener)
}
@facchettos
Copy link

facchettos commented Feb 18, 2019

Or maybe it does, and then re-creates a new one?

Yes I see that's what's happening.

This is because a socket can only be bound once, by recreating a new one, you just make sure that it is a brand new one that will not throw a address already in use exception. Note that if you already have a connection to a previously created socket, this connection will still access the previously created socket. (here with http it is unlikely as connections are short lived, however with classic stream connections this could happen that you have a client that access a socket that is not available anymore to the file system and does not throw an error)

@theferrit32
Copy link

This http.Client transport override is very helpful, thanks for posting! The golang standard library should incorporate a more straightforward way of handling HTTP traffic over unix domain sockets.

@wr125
Copy link

wr125 commented Jul 19, 2019

Nice work!

@teknoraver
Copy link
Author

Thanks!

To preserve the context, I would recommend:

				DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
					dialer := net.Dialer{} // don't know why we need a struct to use DialContext()
					return dialer.DialContext(ctx, "unix", flag.Args()[0])
				},

Hi,

I didn't do this because it wasn't backward compatible with old Go versions

@wr125
Copy link

wr125 commented Jul 19, 2019

I see!

@AlexanderMatveev
Copy link

Thanks!

@DeadlySurgeon
Copy link

Thanks!

To preserve the context, I would recommend:

				DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
					dialer := net.Dialer{} // don't know why we need a struct to use DialContext()
					return dialer.DialContext(ctx, "unix", flag.Args()[0])
				},

I know this is like, 3 years late, but you need to store it as a variable because DialContext belongs to (*Dialer) not (Dialer). When you assign it a local value, it can be referenced as *Dialer. A way around this, is to do this:

return (&net.Dialer{}).DialContext(ctx, "unix", flags.Args()[0])

@alexec
Copy link

alexec commented Sep 15, 2021

I like this. Might be worthwhile mentioning the benefits.

@fthrslntgy
Copy link

Thanks a lot!

Are you sure you can handle the response? response.Body is always empty in my code but I can detect errors or responses at my socket application.

@vinifrancosilva
Copy link

Thank you!

Helped a lot here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment