connectrpc / connect-es

The TypeScript implementation of Connect: Protobuf RPC that works.
https://connectrpc.com/
Apache License 2.0
1.32k stars 75 forks source link

Unix domain socket #756

Open jacobwgillespie opened 1 year ago

jacobwgillespie commented 1 year ago

Is your feature request related to a problem? Please describe.

With other gRPC libraries, I'm able to connect to a gRPC server listening on a unix domain socket (e.g. unix:///path/to/server.sock).

Describe the solution you'd like

I don't think this is possible with createGrpcTransport() at the moment, but it would be awesome if it was.

Describe alternatives you've considered

At the moment, I have to use another gRPC client library - I'd prefer to use @bufbuild/connect instead.

timostamm commented 1 year ago

Unfortunately, the Node.js http2 module does not support UDS out of the box. I assume that it's possible to support this, but it's likely not completely trivial.

In general, we aim to not replicate every gRPC feature, and rely on the standard library of the underlying platform. If Node.js were to support UDS in http2.connect, we'd be happy to make any necessary changes (I assume we'll need to treat baseUrl a bit differently).

If someone comes up with a reliable alternative to http2.connect - demonstrated with a code example - we would also be happy to integrate it, or accept contributions.

jacobwgillespie commented 1 year ago

I may be missing something, but I believe http2 supports UDS. I copied this example from the docs and changed it to bind to a file instead of a port:

import * as http2 from 'node:http2'

const server = http2.createServer()

server.on('stream', (stream, headers) => {
  stream.respond({
    'content-type': 'text/html charset=utf-8',
    ':status': 200,
  })
  stream.end('<h1>Hello World</h1>')
})

// server.listen(8000)
server.listen('./example.sock')
$ curl -v --http2-prior-knowledge --unix-socket ./example.sock http://test
*   Trying ./example.sock:0...
* Connected to test (./example.sock) port 80 (#0)
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: http]
* h2h3 [:authority: test]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x13d80a800)
> GET / HTTP/2
> Host: test
> user-agent: curl/7.88.1
> accept: */*
>
< HTTP/2 200
< content-type: text/html; charset=utf-8
< date: Thu, 31 Aug 2023 08:56:53 GMT
<
* Connection #0 to host test left intact
<h1>Hello World</h1>
timostamm commented 1 year ago

I'm glad you brought up the example, Jacob - I should have been more precise. Yes, you can listen to an UDS with an http2 server (it's a method inherited from net). There is no equivalent for H2 clients though - http2.connect only accepts URLs with the http or https scheme out of the box, see here.

jacobwgillespie commented 1 year ago

Ah, fun. So it looks like createConnection exists in http2.ClientSessionOptions, this appears to work:

http2.connect('http://example', {
  createConnection: (authority, option) => {
    return net.connect('./example.sock');
  },
})

It looks like it's also possible to pass that as an option to new Http2SessionManager(..., ..., {createConnection}) - perhaps that would just work?

caleblloyd commented 3 weeks ago

Yup, I can confirm that this works on Linux. Using @connectrpc/connect-node version 2.0.0-alpha1

import { createGrpcTransport } from "@connectrpc/connect-node";
import { connect as nodeConnect } from "node:net";

const socket = "/path/to/socket"
const transport = createGrpcTransport({
  httpVersion: "2",
  baseUrl: "http://socket.localhost", // not a real URL, socket is in nodeOptions.createConnection
  nodeOptions: {
    createConnection: () => {
      return nodeConnect(socket)
    },
  },
})