Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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)
}
@pbalduino
Copy link

pbalduino commented May 1, 2017

Thank you

@e4jet
Copy link

e4jet commented May 4, 2017

Very nice example!

@yanickxia
Copy link

yanickxia commented May 5, 2017

Cool, Thanks

@jan4984
Copy link

jan4984 commented Jul 6, 2017

Good!

@hanneslehmann
Copy link

hanneslehmann commented Nov 13, 2018

Works without any issues, thanks!

@joonas-fi
Copy link

joonas-fi commented Dec 21, 2018

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])
				},

@jtestard
Copy link

jtestard commented Feb 3, 2019

I am not sure I understand the purpose of the os.Remove(os.Args[1]). It seems that it should delete the socket passed as an argument. However, it doesn't. Also if I remove it, I get a bind: address already in use exception.

@jtestard
Copy link

jtestard commented Feb 3, 2019

It seems that it should delete the socket passed as an argument. However, it doesn't.

Or maybe it does, and then re-creates a new one? I see that after running this code, the permissions on the socket file changed.

@jtestard
Copy link

jtestard commented Feb 3, 2019

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

Yes I see that's what's happening.

@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

theferrit32 commented Mar 11, 2019

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

teknoraver commented Jul 19, 2019

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

AlexanderMatveev commented Nov 6, 2020

Thanks!

@DeadlySurgeon
Copy link

DeadlySurgeon commented Jun 28, 2021

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.

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