net: add SocketAddress.parse
Adds a new `net.SocketAddress.parse(...)` API. ```js const addr = SocketAddress.parse('123.123.123.123:1234'); console.log(addr.address); // 123.123.123.123 console.log(addr.port); 1234 ``` PR-URL: https://github.com/nodejs/node/pull/56076 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
parent
c7de0ec86c
commit
3c2da4b849
@ -244,6 +244,17 @@ added:
|
|||||||
|
|
||||||
* Type {number}
|
* Type {number}
|
||||||
|
|
||||||
|
### `SocketAddress.parse(input)`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `input` {string} An input string containing an IP address and optional port,
|
||||||
|
e.g. `123.1.2.3:1234` or `[1::1]:1234`.
|
||||||
|
* Returns: {net.SocketAddress} Returns a `SocketAddress` if parsing was successful.
|
||||||
|
Otherwise returns `undefined`.
|
||||||
|
|
||||||
## Class: `net.Server`
|
## Class: `net.Server`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
@ -37,6 +37,8 @@ const {
|
|||||||
kDeserialize,
|
kDeserialize,
|
||||||
} = require('internal/worker/js_transferable');
|
} = require('internal/worker/js_transferable');
|
||||||
|
|
||||||
|
const { URL } = require('internal/url');
|
||||||
|
|
||||||
const kHandle = Symbol('kHandle');
|
const kHandle = Symbol('kHandle');
|
||||||
const kDetail = Symbol('kDetail');
|
const kDetail = Symbol('kDetail');
|
||||||
|
|
||||||
@ -74,7 +76,7 @@ class SocketAddress {
|
|||||||
validatePort(port, 'options.port');
|
validatePort(port, 'options.port');
|
||||||
validateUint32(flowlabel, 'options.flowlabel', false);
|
validateUint32(flowlabel, 'options.flowlabel', false);
|
||||||
|
|
||||||
this[kHandle] = new _SocketAddress(address, port, type, flowlabel);
|
this[kHandle] = new _SocketAddress(address, port | 0, type, flowlabel | 0);
|
||||||
this[kDetail] = this[kHandle].detail({
|
this[kDetail] = this[kHandle].detail({
|
||||||
address: undefined,
|
address: undefined,
|
||||||
port: undefined,
|
port: undefined,
|
||||||
@ -138,6 +140,36 @@ class SocketAddress {
|
|||||||
flowlabel: this.flowlabel,
|
flowlabel: this.flowlabel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an "${ip}:${port}" formatted string into a SocketAddress.
|
||||||
|
* Returns undefined if the input cannot be successfully parsed.
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {SocketAddress|undefined}
|
||||||
|
*/
|
||||||
|
static parse(input) {
|
||||||
|
validateString(input, 'input');
|
||||||
|
// While URL.parse is not expected to throw, there are several
|
||||||
|
// other pieces here that do... the destucturing, the SocketAddress
|
||||||
|
// constructor, etc. So we wrap this in a try/catch to be safe.
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
hostname: address,
|
||||||
|
port,
|
||||||
|
} = URL.parse(`http://${input}`);
|
||||||
|
if (address.startsWith('[') && address.endsWith(']')) {
|
||||||
|
return new SocketAddress({
|
||||||
|
address: address.slice(1, -1),
|
||||||
|
port: port | 0,
|
||||||
|
family: 'ipv6',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new SocketAddress({ address, port: port | 0 });
|
||||||
|
} catch {
|
||||||
|
// Ignore errors here. Return undefined if the input cannot
|
||||||
|
// be successfully parsed or is not a proper socket address.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InternalSocketAddress {
|
class InternalSocketAddress {
|
||||||
|
@ -17,121 +17,160 @@ const {
|
|||||||
const { internalBinding } = require('internal/test/binding');
|
const { internalBinding } = require('internal/test/binding');
|
||||||
const {
|
const {
|
||||||
SocketAddress: _SocketAddress,
|
SocketAddress: _SocketAddress,
|
||||||
AF_INET
|
AF_INET,
|
||||||
} = internalBinding('block_list');
|
} = internalBinding('block_list');
|
||||||
|
|
||||||
{
|
const { describe, it } = require('node:test');
|
||||||
const sa = new SocketAddress();
|
|
||||||
strictEqual(sa.address, '127.0.0.1');
|
|
||||||
strictEqual(sa.port, 0);
|
|
||||||
strictEqual(sa.family, 'ipv4');
|
|
||||||
strictEqual(sa.flowlabel, 0);
|
|
||||||
|
|
||||||
const mc = new MessageChannel();
|
describe('net.SocketAddress...', () => {
|
||||||
mc.port1.onmessage = common.mustCall(({ data }) => {
|
|
||||||
ok(SocketAddress.isSocketAddress(data));
|
|
||||||
|
|
||||||
strictEqual(data.address, '127.0.0.1');
|
it('is cloneable', () => {
|
||||||
strictEqual(data.port, 0);
|
const sa = new SocketAddress();
|
||||||
strictEqual(data.family, 'ipv4');
|
strictEqual(sa.address, '127.0.0.1');
|
||||||
strictEqual(data.flowlabel, 0);
|
strictEqual(sa.port, 0);
|
||||||
|
strictEqual(sa.family, 'ipv4');
|
||||||
|
strictEqual(sa.flowlabel, 0);
|
||||||
|
|
||||||
mc.port1.close();
|
const mc = new MessageChannel();
|
||||||
|
mc.port1.onmessage = common.mustCall(({ data }) => {
|
||||||
|
ok(SocketAddress.isSocketAddress(data));
|
||||||
|
|
||||||
|
strictEqual(data.address, '127.0.0.1');
|
||||||
|
strictEqual(data.port, 0);
|
||||||
|
strictEqual(data.family, 'ipv4');
|
||||||
|
strictEqual(data.flowlabel, 0);
|
||||||
|
|
||||||
|
mc.port1.close();
|
||||||
|
});
|
||||||
|
mc.port2.postMessage(sa);
|
||||||
});
|
});
|
||||||
mc.port2.postMessage(sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
it('has reasonable defaults', () => {
|
||||||
const sa = new SocketAddress({});
|
const sa = new SocketAddress({});
|
||||||
strictEqual(sa.address, '127.0.0.1');
|
strictEqual(sa.address, '127.0.0.1');
|
||||||
strictEqual(sa.port, 0);
|
strictEqual(sa.port, 0);
|
||||||
strictEqual(sa.family, 'ipv4');
|
strictEqual(sa.family, 'ipv4');
|
||||||
strictEqual(sa.flowlabel, 0);
|
strictEqual(sa.flowlabel, 0);
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const sa = new SocketAddress({
|
|
||||||
address: '123.123.123.123',
|
|
||||||
});
|
});
|
||||||
strictEqual(sa.address, '123.123.123.123');
|
|
||||||
strictEqual(sa.port, 0);
|
|
||||||
strictEqual(sa.family, 'ipv4');
|
|
||||||
strictEqual(sa.flowlabel, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
it('interprets simple ipv4 correctly', () => {
|
||||||
const sa = new SocketAddress({
|
const sa = new SocketAddress({
|
||||||
address: '123.123.123.123',
|
address: '123.123.123.123',
|
||||||
port: 80
|
});
|
||||||
|
strictEqual(sa.address, '123.123.123.123');
|
||||||
|
strictEqual(sa.port, 0);
|
||||||
|
strictEqual(sa.family, 'ipv4');
|
||||||
|
strictEqual(sa.flowlabel, 0);
|
||||||
});
|
});
|
||||||
strictEqual(sa.address, '123.123.123.123');
|
|
||||||
strictEqual(sa.port, 80);
|
|
||||||
strictEqual(sa.family, 'ipv4');
|
|
||||||
strictEqual(sa.flowlabel, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
it('sets the port correctly', () => {
|
||||||
const sa = new SocketAddress({
|
const sa = new SocketAddress({
|
||||||
family: 'ipv6'
|
address: '123.123.123.123',
|
||||||
|
port: 80
|
||||||
|
});
|
||||||
|
strictEqual(sa.address, '123.123.123.123');
|
||||||
|
strictEqual(sa.port, 80);
|
||||||
|
strictEqual(sa.family, 'ipv4');
|
||||||
|
strictEqual(sa.flowlabel, 0);
|
||||||
});
|
});
|
||||||
strictEqual(sa.address, '::');
|
|
||||||
strictEqual(sa.port, 0);
|
|
||||||
strictEqual(sa.family, 'ipv6');
|
|
||||||
strictEqual(sa.flowlabel, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
it('interprets simple ipv6 correctly', () => {
|
||||||
const sa = new SocketAddress({
|
const sa = new SocketAddress({
|
||||||
family: 'ipv6',
|
family: 'ipv6'
|
||||||
flowlabel: 1,
|
});
|
||||||
|
strictEqual(sa.address, '::');
|
||||||
|
strictEqual(sa.port, 0);
|
||||||
|
strictEqual(sa.family, 'ipv6');
|
||||||
|
strictEqual(sa.flowlabel, 0);
|
||||||
});
|
});
|
||||||
strictEqual(sa.address, '::');
|
|
||||||
strictEqual(sa.port, 0);
|
|
||||||
strictEqual(sa.family, 'ipv6');
|
|
||||||
strictEqual(sa.flowlabel, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[1, false, 'hello'].forEach((i) => {
|
it('uses the flowlabel correctly', () => {
|
||||||
throws(() => new SocketAddress(i), {
|
const sa = new SocketAddress({
|
||||||
code: 'ERR_INVALID_ARG_TYPE'
|
family: 'ipv6',
|
||||||
|
flowlabel: 1,
|
||||||
|
});
|
||||||
|
strictEqual(sa.address, '::');
|
||||||
|
strictEqual(sa.port, 0);
|
||||||
|
strictEqual(sa.family, 'ipv6');
|
||||||
|
strictEqual(sa.flowlabel, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('validates input correctly', () => {
|
||||||
|
[1, false, 'hello'].forEach((i) => {
|
||||||
|
throws(() => new SocketAddress(i), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[1, false, {}, [], 'test'].forEach((family) => {
|
||||||
|
throws(() => new SocketAddress({ family }), {
|
||||||
|
code: 'ERR_INVALID_ARG_VALUE'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[1, false, {}, []].forEach((address) => {
|
||||||
|
throws(() => new SocketAddress({ address }), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[-1, false, {}, []].forEach((port) => {
|
||||||
|
throws(() => new SocketAddress({ port }), {
|
||||||
|
code: 'ERR_SOCKET_BAD_PORT'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
throws(() => new SocketAddress({ flowlabel: -1 }), {
|
||||||
|
code: 'ERR_OUT_OF_RANGE'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('InternalSocketAddress correctly inherits from SocketAddress', () => {
|
||||||
|
// Test that the internal helper class InternalSocketAddress correctly
|
||||||
|
// inherits from SocketAddress and that it does not throw when its properties
|
||||||
|
// are accessed.
|
||||||
|
|
||||||
|
const address = '127.0.0.1';
|
||||||
|
const port = 8080;
|
||||||
|
const flowlabel = 0;
|
||||||
|
const handle = new _SocketAddress(address, port, AF_INET, flowlabel);
|
||||||
|
const addr = new InternalSocketAddress(handle);
|
||||||
|
ok(addr instanceof SocketAddress);
|
||||||
|
strictEqual(addr.address, address);
|
||||||
|
strictEqual(addr.port, port);
|
||||||
|
strictEqual(addr.family, 'ipv4');
|
||||||
|
strictEqual(addr.flowlabel, flowlabel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('SocketAddress.parse() works as expected', () => {
|
||||||
|
const good = [
|
||||||
|
{ input: '1.2.3.4', address: '1.2.3.4', port: 0, family: 'ipv4' },
|
||||||
|
{ input: '192.168.257:1', address: '192.168.1.1', port: 1, family: 'ipv4' },
|
||||||
|
{ input: '256', address: '0.0.1.0', port: 0, family: 'ipv4' },
|
||||||
|
{ input: '999999999:12', address: '59.154.201.255', port: 12, family: 'ipv4' },
|
||||||
|
{ input: '0xffffffff', address: '255.255.255.255', port: 0, family: 'ipv4' },
|
||||||
|
{ input: '0x.0x.0', address: '0.0.0.0', port: 0, family: 'ipv4' },
|
||||||
|
{ input: '[1:0::]', address: '1::', port: 0, family: 'ipv6' },
|
||||||
|
{ input: '[1::8]:123', address: '1::8', port: 123, family: 'ipv6' },
|
||||||
|
];
|
||||||
|
|
||||||
|
good.forEach((i) => {
|
||||||
|
const addr = SocketAddress.parse(i.input);
|
||||||
|
strictEqual(addr.address, i.address);
|
||||||
|
strictEqual(addr.port, i.port);
|
||||||
|
strictEqual(addr.family, i.family);
|
||||||
|
});
|
||||||
|
|
||||||
|
const bad = [
|
||||||
|
'not an ip',
|
||||||
|
'abc.123',
|
||||||
|
'259.1.1.1',
|
||||||
|
'12:12:12',
|
||||||
|
];
|
||||||
|
|
||||||
|
bad.forEach((i) => {
|
||||||
|
strictEqual(SocketAddress.parse(i), undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[1, false, {}, [], 'test'].forEach((family) => {
|
|
||||||
throws(() => new SocketAddress({ family }), {
|
|
||||||
code: 'ERR_INVALID_ARG_VALUE'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
[1, false, {}, []].forEach((address) => {
|
|
||||||
throws(() => new SocketAddress({ address }), {
|
|
||||||
code: 'ERR_INVALID_ARG_TYPE'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
[-1, false, {}, []].forEach((port) => {
|
|
||||||
throws(() => new SocketAddress({ port }), {
|
|
||||||
code: 'ERR_SOCKET_BAD_PORT'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
throws(() => new SocketAddress({ flowlabel: -1 }), {
|
|
||||||
code: 'ERR_OUT_OF_RANGE'
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
// Test that the internal helper class InternalSocketAddress correctly
|
|
||||||
// inherits from SocketAddress and that it does not throw when its properties
|
|
||||||
// are accessed.
|
|
||||||
|
|
||||||
const address = '127.0.0.1';
|
|
||||||
const port = 8080;
|
|
||||||
const flowlabel = 0;
|
|
||||||
const handle = new _SocketAddress(address, port, AF_INET, flowlabel);
|
|
||||||
const addr = new InternalSocketAddress(handle);
|
|
||||||
ok(addr instanceof SocketAddress);
|
|
||||||
strictEqual(addr.address, address);
|
|
||||||
strictEqual(addr.port, port);
|
|
||||||
strictEqual(addr.family, 'ipv4');
|
|
||||||
strictEqual(addr.flowlabel, flowlabel);
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user