-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
First pass at support for TCP servers and clients. #884
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// Copyright 2018 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { ReadResult, Reader, Writer, Closer } from "./io"; | ||
import * as fbs from "gen/msg_generated"; | ||
import { assert, notImplemented } from "./util"; | ||
import * as dispatch from "./dispatch"; | ||
import { flatbuffers } from "flatbuffers"; | ||
import { read, write, close } from "./files"; | ||
|
||
export type Network = "tcp"; | ||
// TODO support other types: | ||
// export type Network = "tcp" | "tcp4" | "tcp6" | "unix" | "unixpacket"; | ||
|
||
// TODO Support finding network from Addr, see https://golang.org/pkg/net/#Addr | ||
export type Addr = string; | ||
|
||
/** A Listener is a generic network listener for stream-oriented protocols. */ | ||
export interface Listener { | ||
/** accept() waits for and returns the next connection to the Listener. */ | ||
accept(): Promise<Conn>; | ||
|
||
/** Close closes the listener. | ||
* Any pending accept promises will be rejected with errors. | ||
*/ | ||
close(): void; | ||
|
||
addr(): Addr; | ||
} | ||
|
||
class ListenerImpl implements Listener { | ||
constructor(readonly fd: number) {} | ||
|
||
async accept(): Promise<Conn> { | ||
const builder = new flatbuffers.Builder(); | ||
fbs.Accept.startAccept(builder); | ||
fbs.Accept.addRid(builder, this.fd); | ||
const msg = fbs.Accept.endAccept(builder); | ||
const baseRes = await dispatch.sendAsync(builder, fbs.Any.Accept, msg); | ||
assert(baseRes != null); | ||
assert(fbs.Any.NewConn === baseRes!.msgType()); | ||
const res = new fbs.NewConn(); | ||
assert(baseRes!.msg(res) != null); | ||
return new ConnImpl(res.rid(), res.remoteAddr()!, res.localAddr()!); | ||
} | ||
|
||
close(): void { | ||
close(this.fd); | ||
} | ||
|
||
addr(): Addr { | ||
return notImplemented(); | ||
} | ||
} | ||
|
||
export interface Conn extends Reader, Writer, Closer { | ||
localAddr: string; | ||
remoteAddr: string; | ||
} | ||
|
||
class ConnImpl implements Conn { | ||
constructor( | ||
readonly fd: number, | ||
readonly remoteAddr: string, | ||
readonly localAddr: string | ||
) {} | ||
|
||
write(p: ArrayBufferView): Promise<number> { | ||
return write(this.fd, p); | ||
} | ||
|
||
read(p: ArrayBufferView): Promise<ReadResult> { | ||
return read(this.fd, p); | ||
} | ||
|
||
close(): void { | ||
close(this.fd); | ||
} | ||
} | ||
|
||
/** Listen announces on the local network address. | ||
* | ||
* The network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket". | ||
* | ||
* For TCP networks, if the host in the address parameter is empty or a literal | ||
* unspecified IP address, Listen listens on all available unicast and anycast | ||
* IP addresses of the local system. To only use IPv4, use network "tcp4". The | ||
* address can use a host name, but this is not recommended, because it will | ||
* create a listener for at most one of the host's IP addresses. If the port in | ||
* the address parameter is empty or "0", as in "127.0.0.1:" or "[::1]:0", a | ||
* port number is automatically chosen. The Addr method of Listener can be used | ||
* to discover the chosen port. | ||
* | ||
* See dial() for a description of the network and address parameters. | ||
*/ | ||
export function listen(network: Network, address: string): Listener { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find "network" very confusing. Why not "protocol" like everybody else and their cat? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It comes from Golang https://golang.org/pkg/net/#Listen There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Still a bad name. |
||
const builder = new flatbuffers.Builder(); | ||
const network_ = builder.createString(network); | ||
const address_ = builder.createString(address); | ||
fbs.Listen.startListen(builder); | ||
fbs.Listen.addNetwork(builder, network_); | ||
fbs.Listen.addAddress(builder, address_); | ||
const msg = fbs.Listen.endListen(builder); | ||
const baseRes = dispatch.sendSync(builder, fbs.Any.Listen, msg); | ||
assert(baseRes != null); | ||
assert(fbs.Any.ListenRes === baseRes!.msgType()); | ||
const res = new fbs.ListenRes(); | ||
assert(baseRes!.msg(res) != null); | ||
return new ListenerImpl(res.rid()); | ||
} | ||
|
||
/** Dial connects to the address on the named network. | ||
* | ||
* Supported networks are only "tcp" currently. | ||
* TODO: "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" | ||
* (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" (IPv6-only), | ||
* "unix", "unixgram" and "unixpacket". | ||
* | ||
* For TCP and UDP networks, the address has the form "host:port". The host must | ||
* be a literal IP address, or a host name that can be resolved to IP addresses. | ||
* The port must be a literal port number or a service name. If the host is a | ||
* literal IPv6 address it must be enclosed in square brackets, as in | ||
* "[2001:db8::1]:80" or "[fe80::1%zone]:80". The zone specifies the scope of | ||
* the literal IPv6 address as defined in RFC 4007. The functions JoinHostPort | ||
* and SplitHostPort manipulate a pair of host and port in this form. When using | ||
* TCP, and the host resolves to multiple IP addresses, Dial will try each IP | ||
* address in order until one succeeds. | ||
* | ||
* Examples: | ||
* | ||
* dial("tcp", "golang.org:http") | ||
* dial("tcp", "192.0.2.1:http") | ||
* dial("tcp", "198.51.100.1:80") | ||
* dial("udp", "[2001:db8::1]:domain") | ||
* dial("udp", "[fe80::1%lo0]:53") | ||
* dial("tcp", ":80") | ||
*/ | ||
export async function dial(network: Network, address: string): Promise<Conn> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sold on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. |
||
const builder = new flatbuffers.Builder(); | ||
const network_ = builder.createString(network); | ||
const address_ = builder.createString(address); | ||
fbs.Dial.startDial(builder); | ||
fbs.Dial.addNetwork(builder, network_); | ||
fbs.Dial.addAddress(builder, address_); | ||
const msg = fbs.Dial.endDial(builder); | ||
const baseRes = await dispatch.sendAsync(builder, fbs.Any.Dial, msg); | ||
assert(baseRes != null); | ||
assert(fbs.Any.NewConn === baseRes!.msgType()); | ||
const res = new fbs.NewConn(); | ||
assert(baseRes!.msg(res) != null); | ||
return new ConnImpl(res.rid(), res.remoteAddr()!, res.localAddr()!); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright 2018 the Deno authors. All rights reserved. MIT license. | ||
|
||
import * as deno from "deno"; | ||
import { testPerm, assert, assertEqual } from "./test_util.ts"; | ||
|
||
testPerm({ net: true }, function netListenClose() { | ||
const listener = deno.listen("tcp", "127.0.0.1:4500"); | ||
listener.close(); | ||
}); | ||
|
||
testPerm({ net: true }, async function netDialListen() { | ||
let addr = "127.0.0.1:4500"; | ||
const listener = deno.listen("tcp", addr); | ||
listener.accept().then(async conn => { | ||
await conn.write(new Uint8Array([1, 2, 3])); | ||
conn.close(); | ||
}); | ||
const conn = await deno.dial("tcp", addr); | ||
const buf = new Uint8Array(1024); | ||
const readResult = await conn.read(buf); | ||
assertEqual(3, readResult.nread); | ||
assertEqual(1, buf[0]); | ||
assertEqual(2, buf[1]); | ||
assertEqual(3, buf[2]); | ||
|
||
// TODO Currently ReadResult does not properly transmit EOF in the same call. | ||
// it requires a second call to get the EOF. Either ReadResult to be an | ||
// integer in which 0 signifies EOF or the handler should be modified so that | ||
// EOF is properly transmitted. | ||
assertEqual(false, readResult.eof); | ||
|
||
const readResult2 = await conn.read(buf); | ||
assertEqual(true, readResult2.eof); | ||
|
||
listener.close(); | ||
conn.close(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm missing something like shutdown here. How is that done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not implemented yet.
It will be added to the AsyncWrite implementation in resources.rs. There's a sub here
deno/src/resources.rs
Lines 104 to 106 in e5e7f0f
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://golang.org/pkg/net/#TCPConn.CloseRead
Following golang I will surface it as
closeRead()
andcloseWrite()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the source code to include a stubbed method.