mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
vlib/net: add buffered IO, x.net -> net (#6754)
This commit is contained in:
100
vlib/net/aasocket.c.v
Normal file
100
vlib/net/aasocket.c.v
Normal file
@@ -0,0 +1,100 @@
|
||||
module net
|
||||
|
||||
// Select represents a select operation
|
||||
enum Select {
|
||||
read write except
|
||||
}
|
||||
|
||||
// SocketType are the available sockets
|
||||
pub enum SocketType {
|
||||
udp = C.SOCK_DGRAM
|
||||
tcp = C.SOCK_STREAM
|
||||
}
|
||||
|
||||
// SocketFamily are the available address families
|
||||
pub enum SocketFamily {
|
||||
inet = C. AF_INET
|
||||
}
|
||||
|
||||
struct C.in_addr {
|
||||
mut:
|
||||
s_addr int
|
||||
}
|
||||
|
||||
struct C.sockaddr {
|
||||
sa_family u16
|
||||
}
|
||||
|
||||
struct C.sockaddr_in {
|
||||
mut:
|
||||
sin_family int
|
||||
sin_port int
|
||||
sin_addr C.in_addr
|
||||
}
|
||||
|
||||
|
||||
struct C.addrinfo {
|
||||
mut:
|
||||
ai_family int
|
||||
ai_socktype int
|
||||
ai_flags int
|
||||
ai_protocol int
|
||||
ai_addrlen int
|
||||
ai_addr voidptr
|
||||
ai_canonname voidptr
|
||||
ai_next voidptr
|
||||
}
|
||||
|
||||
struct C.sockaddr_storage {
|
||||
}
|
||||
|
||||
fn C.socket() int
|
||||
|
||||
fn C.setsockopt() int
|
||||
|
||||
fn C.htonl() int
|
||||
|
||||
fn C.htons() int
|
||||
|
||||
fn C.bind() int
|
||||
|
||||
fn C.listen() int
|
||||
|
||||
fn C.accept() int
|
||||
|
||||
fn C.getaddrinfo() int
|
||||
|
||||
fn C.connect() int
|
||||
|
||||
fn C.send() int
|
||||
fn C.sendto() int
|
||||
|
||||
fn C.recv() int
|
||||
fn C.recvfrom() int
|
||||
|
||||
fn C.shutdown() int
|
||||
|
||||
fn C.ntohs() int
|
||||
|
||||
fn C.getpeername() int
|
||||
|
||||
fn C.inet_ntop(af int, src voidptr, dst charptr, dst_size int) charptr
|
||||
|
||||
fn C.WSAAddressToStringA() int
|
||||
|
||||
fn C.getsockname() int
|
||||
|
||||
// defined in builtin
|
||||
// fn C.read() int
|
||||
// fn C.close() int
|
||||
|
||||
fn C.ioctlsocket() int
|
||||
fn C.fcntl() int
|
||||
|
||||
fn C.@select() int
|
||||
fn C.FD_ZERO()
|
||||
fn C.FD_SET()
|
||||
fn C.FD_ISSET() bool
|
||||
|
||||
[typedef]
|
||||
pub struct C.fd_set {}
|
||||
81
vlib/net/address.v
Normal file
81
vlib/net/address.v
Normal file
@@ -0,0 +1,81 @@
|
||||
module net
|
||||
|
||||
// Addr represents an ip address
|
||||
pub struct Addr {
|
||||
addr C.sockaddr
|
||||
len int
|
||||
pub:
|
||||
saddr string
|
||||
port int
|
||||
}
|
||||
|
||||
pub fn (a Addr) str() string {
|
||||
return '${a.saddr}:${a.port}'
|
||||
}
|
||||
|
||||
const (
|
||||
max_ipv4_addr_len = 24
|
||||
ipv4_addr_size = sizeof(C.sockaddr_in)
|
||||
)
|
||||
|
||||
fn new_addr(addr C.sockaddr) ?Addr {
|
||||
addr_len := if addr.sa_family == SocketFamily.inet {
|
||||
sizeof(C.sockaddr)
|
||||
} else {
|
||||
// TODO NOOOOOOOOOOOO
|
||||
0
|
||||
}
|
||||
// Convert to string representation
|
||||
buf := []byte{ len: max_ipv4_addr_len, init: 0 }
|
||||
$if windows {
|
||||
res := C.WSAAddressToStringA(&addr, addr_len, C.NULL, buf.data, &buf.len)
|
||||
if res != 0 {
|
||||
socket_error(-1)?
|
||||
}
|
||||
} $else {
|
||||
res := C.inet_ntop(SocketFamily.inet, &addr, buf.data, buf.len)
|
||||
if res == 0 {
|
||||
socket_error(-1)?
|
||||
}
|
||||
}
|
||||
mut saddr := buf.bytestr()
|
||||
|
||||
hport := (&C.sockaddr_in(&addr)).sin_port
|
||||
port := C.ntohs(hport)
|
||||
|
||||
$if windows {
|
||||
// strip the port from the address string
|
||||
saddr = saddr.split(':')[0]
|
||||
}
|
||||
|
||||
return Addr {
|
||||
addr int(addr_len) saddr port
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_addr(addr string, family SocketFamily, typ SocketType) ?Addr {
|
||||
address, port := split_address(addr)?
|
||||
|
||||
mut hints := C.addrinfo{}
|
||||
hints.ai_family = family
|
||||
hints.ai_socktype = typ
|
||||
hints.ai_flags = C.AI_PASSIVE
|
||||
hints.ai_protocol = 0
|
||||
hints.ai_addrlen = 0
|
||||
hints.ai_canonname = C.NULL
|
||||
hints.ai_addr = C.NULL
|
||||
hints.ai_next = C.NULL
|
||||
info := &C.addrinfo(0)
|
||||
|
||||
sport := '$port'
|
||||
|
||||
// This might look silly but is recommended by MSDN
|
||||
$if windows {
|
||||
socket_error(0-C.getaddrinfo(address.str, sport.str, &hints, &info))?
|
||||
} $else {
|
||||
x := C.getaddrinfo(address.str, sport.str, &hints, &info)
|
||||
wrap_error(x)?
|
||||
}
|
||||
|
||||
return new_addr(*info.ai_addr)
|
||||
}
|
||||
122
vlib/net/common.v
Normal file
122
vlib/net/common.v
Normal file
@@ -0,0 +1,122 @@
|
||||
module net
|
||||
|
||||
import time
|
||||
|
||||
// Shutdown shutsdown a socket and closes it
|
||||
fn shutdown(handle int) ? {
|
||||
$if windows {
|
||||
C.shutdown(handle, C.SD_BOTH)
|
||||
socket_error(C.closesocket(handle))?
|
||||
} $else {
|
||||
C.shutdown(handle, C.SHUT_RDWR)
|
||||
socket_error(C.close(handle))?
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// Select waits for an io operation (specified by parameter `test`) to be available
|
||||
fn @select(handle int, test Select, timeout time.Duration) ?bool {
|
||||
set := C.fd_set{}
|
||||
|
||||
C.FD_ZERO(&set)
|
||||
C.FD_SET(handle, &set)
|
||||
|
||||
seconds := timeout.milliseconds() / 1000
|
||||
microseconds := timeout - (seconds * time.second)
|
||||
|
||||
mut tt := C.timeval{
|
||||
tv_sec: u64(seconds)
|
||||
tv_usec: u64(microseconds)
|
||||
}
|
||||
|
||||
mut timeval_timeout := &tt
|
||||
|
||||
// infinite timeout is signaled by passing null as the timeout to
|
||||
// select
|
||||
if timeout == infinite_timeout {
|
||||
timeval_timeout = &C.timeval(0)
|
||||
}
|
||||
|
||||
match test {
|
||||
.read {
|
||||
socket_error(C.@select(handle+1, &set, C.NULL, C.NULL, timeval_timeout))?
|
||||
}
|
||||
.write {
|
||||
socket_error(C.@select(handle+1, C.NULL, &set, C.NULL, timeval_timeout))?
|
||||
}
|
||||
.except {
|
||||
socket_error(C.@select(handle+1, C.NULL, C.NULL, &set, timeval_timeout))?
|
||||
}
|
||||
}
|
||||
|
||||
return C.FD_ISSET(handle, &set)
|
||||
}
|
||||
|
||||
// wait_for_common wraps the common wait code
|
||||
fn wait_for_common(
|
||||
handle int,
|
||||
deadline time.Time,
|
||||
timeout time.Duration,
|
||||
test Select) ? {
|
||||
if deadline.unix == 0 {
|
||||
// only accept infinite_timeout as a valid
|
||||
// negative timeout - it is handled in @select however
|
||||
if timeout < 0 && timeout != infinite_timeout {
|
||||
return err_timed_out
|
||||
}
|
||||
ready := @select(handle, test, timeout)?
|
||||
if ready {
|
||||
return none
|
||||
}
|
||||
return err_timed_out
|
||||
}
|
||||
// Convert the deadline into a timeout
|
||||
// and use that
|
||||
d_timeout := deadline.unix - time.now().unix
|
||||
if d_timeout < 0 {
|
||||
// deadline is in the past so this has already
|
||||
// timed out
|
||||
return err_timed_out
|
||||
}
|
||||
|
||||
ready := @select(handle, test, d_timeout)?
|
||||
if ready {
|
||||
return none
|
||||
}
|
||||
return err_timed_out
|
||||
}
|
||||
|
||||
// wait_for_write waits for a write io operation to be available
|
||||
fn wait_for_write(
|
||||
handle int,
|
||||
deadline time.Time,
|
||||
timeout time.Duration) ? {
|
||||
return wait_for_common(handle, deadline, timeout, .write)
|
||||
}
|
||||
|
||||
// wait_for_read waits for a read io operation to be available
|
||||
fn wait_for_read(
|
||||
handle int,
|
||||
deadline time.Time,
|
||||
timeout time.Duration) ? {
|
||||
return wait_for_common(handle, deadline, timeout, .read)
|
||||
}
|
||||
|
||||
// no_deadline should be given to functions when no deadline is wanted (i.e. all functions
|
||||
// return instantly)
|
||||
const (
|
||||
no_deadline = time.Time{unix: 0}
|
||||
)
|
||||
|
||||
// no_timeout should be given to functions when no timeout is wanted (i.e. all functions
|
||||
// return instantly)
|
||||
const (
|
||||
no_timeout = time.Duration(0)
|
||||
)
|
||||
|
||||
// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions
|
||||
// only ever return with data)
|
||||
const (
|
||||
infinite_timeout = time.Duration(-1)
|
||||
)
|
||||
59
vlib/net/errors.v
Normal file
59
vlib/net/errors.v
Normal file
@@ -0,0 +1,59 @@
|
||||
module net
|
||||
|
||||
// Well defined errors that are returned from socket functions
|
||||
const (
|
||||
errors_base = 0
|
||||
err_new_socket_failed = error_with_code('net: new_socket failed to create socket', errors_base+1)
|
||||
err_option_not_settable = error_with_code('net: set_option_xxx option not settable', errors_base+2)
|
||||
err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type', errors_base+3)
|
||||
err_port_out_of_range = error_with_code('', errors_base+5)
|
||||
err_no_udp_remote = error_with_code('', errors_base+6)
|
||||
err_connect_failed = error_with_code('net: connect failed', errors_base+7)
|
||||
err_connect_timed_out = error_with_code('net: connect timed out', errors_base+8)
|
||||
|
||||
err_timed_out = error_with_code('net: op timed out', errors_base+9)
|
||||
err_timed_out_code = errors_base+9
|
||||
)
|
||||
|
||||
pub fn socket_error(potential_code int) ?int {
|
||||
$if windows {
|
||||
if potential_code < 0 {
|
||||
last_error_int := C.WSAGetLastError()
|
||||
last_error := wsa_error(last_error_int)
|
||||
return error_with_code('net: socket error: ($last_error_int) $last_error', last_error)
|
||||
}
|
||||
}
|
||||
$else {
|
||||
if potential_code < 0 {
|
||||
last_error := error_code()
|
||||
return error_with_code('net: socket error: $last_error', last_error)
|
||||
}
|
||||
}
|
||||
|
||||
return potential_code
|
||||
}
|
||||
|
||||
pub fn wrap_error(error_code int) ? {
|
||||
$if windows {
|
||||
enum_error := wsa_error(error_code)
|
||||
return error_with_code('socket error: $enum_error', error_code)
|
||||
}
|
||||
$else {
|
||||
if error_code == 0 {
|
||||
return
|
||||
}
|
||||
return error_with_code('net: socket error: $error_code', error_code)
|
||||
}
|
||||
}
|
||||
|
||||
// wrap_read_result takes a read result and sees if it is 0 for graceful
|
||||
// connection termination and returns none
|
||||
// e.g. res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))?
|
||||
[inline]
|
||||
fn wrap_read_result(result int) ?int {
|
||||
if result > 0 || result < 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
module ftp
|
||||
|
||||
/*
|
||||
basic ftp module
|
||||
RFC-959
|
||||
@@ -15,9 +17,9 @@ basic ftp module
|
||||
dtp.close()
|
||||
ftp.close()
|
||||
*/
|
||||
module ftp
|
||||
|
||||
import net
|
||||
import io
|
||||
|
||||
const (
|
||||
connected = 220
|
||||
@@ -35,33 +37,26 @@ const (
|
||||
|
||||
struct DTP {
|
||||
mut:
|
||||
sock net.Socket
|
||||
conn net.TcpConn
|
||||
reader io.BufferedReader
|
||||
ip string
|
||||
port int
|
||||
}
|
||||
|
||||
fn (dtp DTP) read() []byte {
|
||||
mut data := []byte{}
|
||||
for {
|
||||
buf, len := dtp.sock.recv(1024)
|
||||
if len == 0 {
|
||||
break
|
||||
}
|
||||
for i in 0 .. len {
|
||||
data << unsafe {buf[i]}
|
||||
}
|
||||
unsafe {free(buf)}
|
||||
}
|
||||
fn (mut dtp DTP) read() ?[]byte {
|
||||
mut data := []byte{len: 1024}
|
||||
dtp.reader.read(mut data)?
|
||||
return data
|
||||
}
|
||||
|
||||
fn (dtp DTP) close() {
|
||||
dtp.sock.close() or { }
|
||||
dtp.conn.close()
|
||||
}
|
||||
|
||||
struct FTP {
|
||||
mut:
|
||||
sock net.Socket
|
||||
conn net.TcpConn
|
||||
reader io.BufferedReader
|
||||
buffer_size int
|
||||
}
|
||||
|
||||
@@ -71,18 +66,15 @@ pub fn new() FTP {
|
||||
return f
|
||||
}
|
||||
|
||||
fn (ftp FTP) write(data string) ?int {
|
||||
fn (mut ftp FTP) write(data string) ? {
|
||||
$if debug {
|
||||
println('FTP.v >>> $data')
|
||||
}
|
||||
n := ftp.sock.send_string('$data\r\n') or {
|
||||
return error('Cannot send data')
|
||||
}
|
||||
return n
|
||||
ftp.conn.write('$data\r\n'.bytes())?
|
||||
}
|
||||
|
||||
fn (ftp FTP) read() (int, string) {
|
||||
mut data := ftp.sock.read_line()
|
||||
fn (mut ftp FTP) read() ?(int, string) {
|
||||
mut data := ftp.reader.read_line()?
|
||||
$if debug {
|
||||
println('FTP.v <<< $data')
|
||||
}
|
||||
@@ -92,7 +84,7 @@ fn (ftp FTP) read() (int, string) {
|
||||
code := data[..3].int()
|
||||
if data[3] == `-` {
|
||||
for {
|
||||
data = ftp.sock.read_line()
|
||||
data = ftp.reader.read_line()?
|
||||
if data[..3].int() == code && data[3] != `-` {
|
||||
break
|
||||
}
|
||||
@@ -101,26 +93,25 @@ fn (ftp FTP) read() (int, string) {
|
||||
return code, data
|
||||
}
|
||||
|
||||
pub fn (mut ftp FTP) connect(ip string) bool {
|
||||
sock := net.dial(ip, 21) or {
|
||||
return false
|
||||
}
|
||||
ftp.sock = sock
|
||||
code, _ := ftp.read()
|
||||
pub fn (mut ftp FTP) connect(ip string) ?bool {
|
||||
conn := net.dial_tcp('$ip:21')?
|
||||
ftp.conn = conn
|
||||
code, _ := ftp.read()?
|
||||
if code == connected {
|
||||
return true
|
||||
}
|
||||
ftp.reader = io.new_buffered_reader(reader: io.make_reader(ftp.conn))
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (ftp FTP) login(user string, passwd string) bool {
|
||||
pub fn (mut ftp FTP) login(user string, passwd string) ?bool {
|
||||
ftp.write('USER $user') or {
|
||||
$if debug {
|
||||
println('ERROR sending user')
|
||||
}
|
||||
return false
|
||||
}
|
||||
mut code, _ := ftp.read()
|
||||
mut code, _ := ftp.read()?
|
||||
if code == logged_in {
|
||||
return true
|
||||
}
|
||||
@@ -133,24 +124,21 @@ pub fn (ftp FTP) login(user string, passwd string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
code, _ = ftp.read()
|
||||
code, _ = ftp.read()?
|
||||
if code == logged_in {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (ftp FTP) close() {
|
||||
send_quit := 'QUIT\r\n'
|
||||
ftp.sock.send_string(send_quit) or { }
|
||||
ftp.sock.close() or { }
|
||||
pub fn ( mut ftp FTP) close() ? {
|
||||
ftp.write('QUIT')?
|
||||
ftp.conn.close()
|
||||
}
|
||||
|
||||
pub fn (ftp FTP) pwd() string {
|
||||
ftp.write('PWD') or {
|
||||
return ''
|
||||
}
|
||||
_, data := ftp.read()
|
||||
pub fn ( mut ftp FTP) pwd() ?string {
|
||||
ftp.write('PWD')?
|
||||
_, data := ftp.read()?
|
||||
spl := data.split('"') // "
|
||||
if spl.len >= 2 {
|
||||
return spl[1]
|
||||
@@ -158,11 +146,11 @@ pub fn (ftp FTP) pwd() string {
|
||||
return data
|
||||
}
|
||||
|
||||
pub fn (ftp FTP) cd(dir string) {
|
||||
pub fn ( mut ftp FTP) cd(dir string) ? {
|
||||
ftp.write('CWD $dir') or {
|
||||
return
|
||||
}
|
||||
mut code, mut data := ftp.read()
|
||||
mut code, mut data := ftp.read()?
|
||||
match int(code) {
|
||||
denied {
|
||||
$if debug {
|
||||
@@ -170,7 +158,7 @@ pub fn (ftp FTP) cd(dir string) {
|
||||
}
|
||||
}
|
||||
complete {
|
||||
code, data = ftp.read()
|
||||
code, data = ftp.read()?
|
||||
}
|
||||
else {}
|
||||
}
|
||||
@@ -184,20 +172,21 @@ fn new_dtp(msg string) ?DTP {
|
||||
return error('Bad message')
|
||||
}
|
||||
ip, port := get_host_ip_from_dtp_message(msg)
|
||||
sock := net.dial(ip, port) or {
|
||||
conn := net.dial_tcp('$ip:$port') or {
|
||||
return error('Cannot connect to the data channel')
|
||||
}
|
||||
dtp := DTP{
|
||||
sock: sock
|
||||
conn: conn
|
||||
ip: ip
|
||||
port: port
|
||||
}
|
||||
return dtp
|
||||
}
|
||||
|
||||
fn (ftp FTP) pasv() ?DTP {
|
||||
ftp.write('PASV') or { }
|
||||
code, data := ftp.read()
|
||||
fn ( mut ftp FTP) pasv() ?DTP {
|
||||
ftp.write('PASV') or {
|
||||
}
|
||||
code, data := ftp.read()?
|
||||
$if debug {
|
||||
println('pass: $data')
|
||||
}
|
||||
@@ -208,20 +197,21 @@ fn (ftp FTP) pasv() ?DTP {
|
||||
return dtp
|
||||
}
|
||||
|
||||
pub fn (ftp FTP) dir() ?[]string {
|
||||
dtp := ftp.pasv() or {
|
||||
pub fn ( mut ftp FTP) dir() ?[]string {
|
||||
mut dtp := ftp.pasv() or {
|
||||
return error('cannot establish data connection')
|
||||
}
|
||||
ftp.write('LIST') or { }
|
||||
code, _ := ftp.read()
|
||||
ftp.write('LIST') or {
|
||||
}
|
||||
code, _ := ftp.read()?
|
||||
if code == denied {
|
||||
return error('LIST denied')
|
||||
}
|
||||
if code != open_data_connection {
|
||||
return error('data channel empty')
|
||||
}
|
||||
list_dir := dtp.read()
|
||||
result, _ := ftp.read()
|
||||
list_dir := dtp.read()?
|
||||
result, _ := ftp.read()?
|
||||
if result != close_data_connection {
|
||||
println('LIST not ok')
|
||||
}
|
||||
@@ -237,19 +227,20 @@ pub fn (ftp FTP) dir() ?[]string {
|
||||
return dir
|
||||
}
|
||||
|
||||
pub fn (ftp FTP) get(file string) ?[]byte {
|
||||
dtp := ftp.pasv() or {
|
||||
pub fn (mut ftp FTP) get(file string) ?[]byte {
|
||||
mut dtp := ftp.pasv() or {
|
||||
return error('Cannot stablish data connection')
|
||||
}
|
||||
ftp.write('RETR $file') or { }
|
||||
code, _ := ftp.read()
|
||||
ftp.write('RETR $file') or {
|
||||
}
|
||||
code, _ := ftp.read()?
|
||||
if code == denied {
|
||||
return error('Permission denied')
|
||||
}
|
||||
if code != open_data_connection {
|
||||
return error('Data connection not ready')
|
||||
}
|
||||
blob := dtp.read()
|
||||
blob := dtp.read()?
|
||||
dtp.close()
|
||||
return blob
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@ import net.ftp
|
||||
// NB: this function makes network calls to external servers,
|
||||
// that is why it is not a very good idea to run it in CI.
|
||||
// If you want to run it manually, use `v -d network vlib/net/ftp/ftp_test.v`
|
||||
fn test_ftp_client() {
|
||||
fn ftp_client_test_inside() ? {
|
||||
$if !network ? { return }
|
||||
mut ftp := ftp.new()
|
||||
defer {
|
||||
ftp.close()
|
||||
}
|
||||
assert ftp.connect('ftp.redhat.com')
|
||||
assert ftp.login('ftp', 'ftp')
|
||||
pwd := ftp.pwd()
|
||||
connect_result := ftp.connect('ftp.redhat.com')?
|
||||
assert connect_result
|
||||
login_result := ftp.login('ftp', 'ftp')?
|
||||
assert login_result
|
||||
pwd := ftp.pwd()?
|
||||
assert pwd.len > 0
|
||||
ftp.cd('/')
|
||||
dir_list1 := ftp.dir() or {
|
||||
@@ -31,3 +33,10 @@ fn test_ftp_client() {
|
||||
}
|
||||
assert blob.len > 0
|
||||
}
|
||||
|
||||
|
||||
fn test_ftp_cleint() {
|
||||
ftp_client_test_inside() or {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ module http
|
||||
|
||||
import net.urllib
|
||||
import net.http.chunked
|
||||
import strings
|
||||
import net
|
||||
import time
|
||||
import io
|
||||
|
||||
const (
|
||||
max_redirects = 4
|
||||
@@ -276,7 +277,7 @@ fn (req &Request) method_and_url_to_response(method Method, url urllib.URL) ?Res
|
||||
return res
|
||||
} else if scheme == 'http' {
|
||||
// println('http_do( $nport, $method, $host_name, $path )')
|
||||
res := req.http_do(nport, method, host_name, path) ?
|
||||
res := req.http_do('$host_name:$nport', method, path)?
|
||||
return res
|
||||
}
|
||||
return error('http.request.method_and_url_to_response: unsupported scheme: "$scheme"')
|
||||
@@ -392,24 +393,16 @@ pub fn escape(s string) string {
|
||||
panic('http.escape() was replaced with http.escape_url()')
|
||||
}
|
||||
|
||||
fn (req &Request) http_do(port int, method Method, host_name string, path string) ?Response {
|
||||
rbuffer := [bufsize]byte{}
|
||||
mut sb := strings.new_builder(100)
|
||||
fn (req &Request) http_do(host string, method Method, path string) ?Response {
|
||||
host_name, _ := net.split_address(host)?
|
||||
s := req.build_request_headers(method, host_name, path)
|
||||
client := net.dial(host_name, port) ?
|
||||
client.send(s.str, s.len) or { }
|
||||
for {
|
||||
readbytes := client.crecv(rbuffer, bufsize)
|
||||
if readbytes < 0 {
|
||||
return error('http.request.http_do: error reading response. readbytes=$readbytes')
|
||||
}
|
||||
if readbytes == 0 {
|
||||
break
|
||||
}
|
||||
sb.write(tos(rbuffer, readbytes))
|
||||
}
|
||||
client.close() or { }
|
||||
return parse_response(sb.str())
|
||||
mut client := net.dial_tcp(host)?
|
||||
// TODO this really needs to be exposed somehow
|
||||
client.set_read_timeout(time.second * 30)
|
||||
client.write(s.bytes())?
|
||||
mut bytes := io.read_all(client)?
|
||||
client.close()
|
||||
return parse_response(bytes.bytestr())
|
||||
}
|
||||
|
||||
pub fn (req &Request) referer() string {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
module net
|
||||
|
||||
#flag -lws2_32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
struct C.WSAData {
|
||||
mut:
|
||||
wVersion u16
|
||||
wHighVersion u16
|
||||
szDescription [257]byte
|
||||
szSystemStatus [129]byte
|
||||
iMaxSockets u16
|
||||
iMaxUdpDg u16
|
||||
lpVendorInfo byteptr
|
||||
}
|
||||
|
||||
|
||||
const (
|
||||
wsa_v22 = 0x202 // C.MAKEWORD(2, 2)
|
||||
)
|
||||
|
||||
fn init() {
|
||||
mut wsadata := C.WSAData{}
|
||||
res := C.WSAStartup(wsa_v22, &wsadata)
|
||||
if res != 0 {
|
||||
panic('socket: WSAStartup failed')
|
||||
}
|
||||
}
|
||||
|
||||
fn error_code() int {
|
||||
return C.WSAGetLastError()
|
||||
}
|
||||
|
||||
pub const (
|
||||
msg_nosignal = 0
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
module net
|
||||
|
||||
fn C.gethostname() int
|
||||
// hostname returns the host name reported by the kernel.
|
||||
pub fn hostname() ?string {
|
||||
mut name := [256]byte{}
|
||||
// https://www.ietf.org/rfc/rfc1035.txt
|
||||
// The host name is returned as a null-terminated string.
|
||||
namebp := byteptr(name)
|
||||
res := C.gethostname(namebp, 256)
|
||||
if res != 0 {
|
||||
return error('net.hostname: failed with $res')
|
||||
}
|
||||
return tos_clone(namebp)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
module net
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/select.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
fn error_code() int {
|
||||
return C.errno
|
||||
}
|
||||
|
||||
fn init() {
|
||||
}
|
||||
|
||||
pub const (
|
||||
msg_nosignal = 0x4000
|
||||
)
|
||||
|
||||
const (
|
||||
error_ewouldblock = C.EWOULDBLOCK
|
||||
)
|
||||
|
||||
#flag solaris -lsocket
|
||||
868
vlib/net/net_windows.c.v
Normal file
868
vlib/net/net_windows.c.v
Normal file
@@ -0,0 +1,868 @@
|
||||
module net
|
||||
|
||||
// WsaError is all of the socket errors that WSA provides from WSAGetLastError
|
||||
pub enum WsaError {
|
||||
//
|
||||
// MessageId: WSAEINTR
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A blocking operation was interrupted by a call to WSACancelBlockingCall.
|
||||
//
|
||||
wsaeintr = 10004
|
||||
|
||||
//
|
||||
// MessageId: WSAEBADF
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The file handle supplied is not valid.
|
||||
//
|
||||
wsaebadf = 10009
|
||||
|
||||
//
|
||||
// MessageId: WSAEACCES
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An attempt was made to access a socket in a way forbidden by its access permissions.
|
||||
//
|
||||
wsaeacces = 10013
|
||||
|
||||
//
|
||||
// MessageId: WSAEFAULT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The system detected an invalid pointer address in attempting to use a pointer argument in a call.
|
||||
//
|
||||
wsaefault = 10014
|
||||
|
||||
//
|
||||
// MessageId: WSAEINVAL
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid argument was supplied.
|
||||
//
|
||||
wsaeinval = 10022
|
||||
|
||||
//
|
||||
// MessageId: WSAEMFILE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Too many open sockets.
|
||||
//
|
||||
wsaemfile = 10024
|
||||
|
||||
//
|
||||
// MessageId: WSAEWOULDBLOCK
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A non-blocking socket operation could not be completed immediately.
|
||||
//
|
||||
wsaewouldblock = 10035
|
||||
|
||||
//
|
||||
// MessageId: WSAEINPROGRESS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A blocking operation is currently executing.
|
||||
//
|
||||
wsaeinprogress = 10036
|
||||
|
||||
//
|
||||
// MessageId: WSAEALREADY
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An operation was attempted on a non-blocking socket that already had an operation in progress.
|
||||
//
|
||||
wsaealready = 10037
|
||||
|
||||
//
|
||||
// MessageId: WSAENOTSOCK
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An operation was attempted on something that is not a socket.
|
||||
//
|
||||
wsaenotsock = 10038
|
||||
|
||||
//
|
||||
// MessageId: WSAEDESTADDRREQ
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A required address was omitted from an operation on a socket.
|
||||
//
|
||||
wsaedestaddrreq = 10039
|
||||
|
||||
//
|
||||
// MessageId: WSAEMSGSIZE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself.
|
||||
//
|
||||
wsaemsgsize = 10040
|
||||
|
||||
//
|
||||
// MessageId: WSAEPROTOTYPE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A protocol was specified in the socket function call that does not support the semantics of the socket type requested.
|
||||
//
|
||||
wsaeprototype = 10041
|
||||
|
||||
//
|
||||
// MessageId: WSAENOPROTOOPT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call.
|
||||
//
|
||||
wsaenoprotoopt = 10042
|
||||
|
||||
//
|
||||
// MessageId: WSAEPROTONOSUPPORT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The requested protocol has not been configured into the system, or no implementation for it exists.
|
||||
//
|
||||
wsaeprotonosupport = 10043
|
||||
|
||||
//
|
||||
// MessageId: WSAESOCKTNOSUPPORT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The support for the specified socket type does not exist in this address family.
|
||||
//
|
||||
wsaesocktnosupport = 10044
|
||||
|
||||
//
|
||||
// MessageId: WSAEOPNOTSUPP
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The attempted operation is not supported for the type of object referenced.
|
||||
//
|
||||
wsaeopnotsupp = 10045
|
||||
|
||||
//
|
||||
// MessageId: WSAEPFNOSUPPORT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The protocol family has not been configured into the system or no implementation for it exists.
|
||||
//
|
||||
wsaepfnosupport = 10046
|
||||
|
||||
//
|
||||
// MessageId: WSAEAFNOSUPPORT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An address incompatible with the requested protocol was used.
|
||||
//
|
||||
wsaeafnosupport = 10047
|
||||
|
||||
//
|
||||
// MessageId: WSAEADDRINUSE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Only one usage of each socket address (protocol/network address/port) is normally permitted.
|
||||
//
|
||||
wsaeaddrinuse = 10048
|
||||
|
||||
//
|
||||
// MessageId: WSAEADDRNOTAVAIL
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The requested address is not valid in its context.
|
||||
//
|
||||
wsaeaddrnotavail = 10049
|
||||
|
||||
//
|
||||
// MessageId: WSAENETDOWN
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A socket operation encountered a dead network.
|
||||
//
|
||||
wsaenetdown = 10050
|
||||
|
||||
//
|
||||
// MessageId: WSAENETUNREACH
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A socket operation was attempted to an unreachable network.
|
||||
//
|
||||
wsaenetunreach = 10051
|
||||
|
||||
//
|
||||
// MessageId: WSAENETRESET
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress.
|
||||
//
|
||||
wsaenetreset = 10052
|
||||
|
||||
//
|
||||
// MessageId: WSAECONNABORTED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An established connection was aborted by the software in your host machine.
|
||||
//
|
||||
wsaeconnaborted = 10053
|
||||
|
||||
//
|
||||
// MessageId: WSAECONNRESET
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An existing connection was forcibly closed by the remote host.
|
||||
//
|
||||
wsaeconnreset = 10054
|
||||
|
||||
//
|
||||
// MessageId: WSAENOBUFS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
|
||||
//
|
||||
wsaenobufs = 10055
|
||||
|
||||
//
|
||||
// MessageId: WSAEISCONN
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A connect request was made on an already connected socket.
|
||||
//
|
||||
wsaeisconn = 10056
|
||||
|
||||
//
|
||||
// MessageId: WSAENOTCONN
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied.
|
||||
//
|
||||
wsaenotconn = 10057
|
||||
|
||||
//
|
||||
// MessageId: WSAESHUTDOWN
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call.
|
||||
//
|
||||
wsaeshutdown = 10058
|
||||
|
||||
//
|
||||
// MessageId: WSAETOOMANYREFS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Too many references to some kernel object.
|
||||
//
|
||||
wsaetoomanyrefs = 10059
|
||||
|
||||
//
|
||||
// MessageId: WSAETIMEDOUT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
|
||||
//
|
||||
wsaetimedout = 10060
|
||||
|
||||
//
|
||||
// MessageId: WSAECONNREFUSED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// No connection could be made because the target machine actively refused it.
|
||||
//
|
||||
wsaeconnrefused = 10061
|
||||
|
||||
//
|
||||
// MessageId: WSAELOOP
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Cannot translate name.
|
||||
//
|
||||
wsaeloop = 10062
|
||||
|
||||
//
|
||||
// MessageId: WSAENAMETOOLONG
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Name component or name was too long.
|
||||
//
|
||||
wsaenametoolong = 10063
|
||||
|
||||
//
|
||||
// MessageId: WSAEHOSTDOWN
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A socket operation failed because the destination host was down.
|
||||
//
|
||||
wsaehostdown = 10064
|
||||
|
||||
//
|
||||
// MessageId: WSAEHOSTUNREACH
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A socket operation was attempted to an unreachable host.
|
||||
//
|
||||
wsaehostunreach = 10065
|
||||
|
||||
//
|
||||
// MessageId: WSAENOTEMPTY
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Cannot remove a directory that is not empty.
|
||||
//
|
||||
wsaenotempty = 10066
|
||||
|
||||
//
|
||||
// MessageId: WSAEPROCLIM
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously.
|
||||
//
|
||||
wsaeproclim = 10067
|
||||
|
||||
//
|
||||
// MessageId: WSAEUSERS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Ran out of quota.
|
||||
//
|
||||
wsaeusers = 10068
|
||||
|
||||
//
|
||||
// MessageId: WSAEDQUOT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Ran out of disk quota.
|
||||
//
|
||||
wsaedquot = 10069
|
||||
|
||||
//
|
||||
// MessageId: WSAESTALE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// File handle reference is no longer available.
|
||||
//
|
||||
wsaestale = 10070
|
||||
|
||||
//
|
||||
// MessageId: WSAEREMOTE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Item is not available locally.
|
||||
//
|
||||
wsaeremote = 10071
|
||||
|
||||
//
|
||||
// MessageId: WSASYSNOTREADY
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable.
|
||||
//
|
||||
wsasysnotready = 10091
|
||||
|
||||
//
|
||||
// MessageId: WSAVERNOTSUPPORTED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The Windows Sockets version requested is not supported.
|
||||
//
|
||||
wsavernotsupported = 10092
|
||||
|
||||
//
|
||||
// MessageId: WSANOTINITIALISED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Either the application has not called WSAStartup, or WSAStartup failed.
|
||||
//
|
||||
wsanotinitialised = 10093
|
||||
|
||||
//
|
||||
// MessageId: WSAEDISCON
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence.
|
||||
//
|
||||
wsaediscon = 10101
|
||||
|
||||
//
|
||||
// MessageId: WSAENOMORE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// No more results can be returned by WSALookupServiceNext.
|
||||
//
|
||||
wsaenomore = 10102
|
||||
|
||||
//
|
||||
// MessageId: WSAECANCELLED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled.
|
||||
//
|
||||
wsaecancelled = 10103
|
||||
|
||||
//
|
||||
// MessageId: WSAEINVALIDPROCTABLE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The procedure call table is invalid.
|
||||
//
|
||||
wsaeinvalidproctable = 10104
|
||||
|
||||
//
|
||||
// MessageId: WSAEINVALIDPROVIDER
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The requested service provider is invalid.
|
||||
//
|
||||
wsaeinvalidprovider = 10105
|
||||
|
||||
//
|
||||
// MessageId: WSAEPROVIDERFAILEDINIT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The requested service provider could not be loaded or initialized.
|
||||
//
|
||||
wsaeproviderfailedinit = 10106
|
||||
|
||||
//
|
||||
// MessageId: WSASYSCALLFAILURE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A system call has failed.
|
||||
//
|
||||
wsasyscallfailure = 10107
|
||||
|
||||
//
|
||||
// MessageId: WSASERVICE_NOT_FOUND
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// No such service is known. The service cannot be found in the specified name space.
|
||||
//
|
||||
wsaservice_not_found = 10108
|
||||
|
||||
//
|
||||
// MessageId: WSATYPE_NOT_FOUND
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The specified class was not found.
|
||||
//
|
||||
wsatype_not_found = 10109
|
||||
|
||||
//
|
||||
// MessageId: WSA_E_NO_MORE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// No more results can be returned by WSALookupServiceNext.
|
||||
//
|
||||
wsa_e_no_more = 10110
|
||||
|
||||
//
|
||||
// MessageId: WSA_E_CANCELLED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled.
|
||||
//
|
||||
wsa_e_cancelled = 10111
|
||||
|
||||
//
|
||||
// MessageId: WSAEREFUSED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A database query failed because it was actively refused.
|
||||
//
|
||||
wsaerefused = 10112
|
||||
|
||||
//
|
||||
// MessageId: WSAHOST_NOT_FOUND
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// No such host is known.
|
||||
//
|
||||
wsahost_not_found = 11001
|
||||
|
||||
//
|
||||
// MessageId: WSATRY_AGAIN
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server.
|
||||
//
|
||||
wsatry_again = 11002
|
||||
|
||||
//
|
||||
// MessageId: WSANO_RECOVERY
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A non-recoverable error occurred during a database lookup.
|
||||
//
|
||||
wsano_recovery = 11003
|
||||
|
||||
//
|
||||
// MessageId: WSANO_DATA
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// The requested name is valid, but no data of the requested type was found.
|
||||
//
|
||||
wsano_data = 11004
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_RECEIVERS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// At least one reserve has arrived.
|
||||
//
|
||||
wsa_qos_receivers = 11005
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_SENDERS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// At least one path has arrived.
|
||||
//
|
||||
wsa_qos_senders = 11006
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_NO_SENDERS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// There are no senders.
|
||||
//
|
||||
wsa_qos_no_senders = 11007
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_NO_RECEIVERS
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// There are no receivers.
|
||||
//
|
||||
wsa_qos_no_receivers = 11008
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_REQUEST_CONFIRMED
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Reserve has been confirmed.
|
||||
//
|
||||
wsa_qos_request_confirmed = 11009
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_ADMISSION_FAILURE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Error due to lack of resources.
|
||||
//
|
||||
wsa_qos_admission_failure = 11010
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_POLICY_FAILURE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Rejected for administrative reasons - bad credentials.
|
||||
//
|
||||
wsa_qos_policy_failure = 11011
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_BAD_STYLE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Unknown or conflicting style.
|
||||
//
|
||||
wsa_qos_bad_style = 11012
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_BAD_OBJECT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Problem with some part of the filterspec or providerspecific buffer in general.
|
||||
//
|
||||
wsa_qos_bad_object = 11013
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_TRAFFIC_CTRL_ERROR
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Problem with some part of the flowspec.
|
||||
//
|
||||
wsa_qos_traffic_ctrl_error = 11014
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_GENERIC_ERROR
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// General QOS error.
|
||||
//
|
||||
wsa_qos_generic_error = 11015
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_ESERVICETYPE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid or unrecognized service type was found in the flowspec.
|
||||
//
|
||||
wsa_qos_eservicetype = 11016
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EFLOWSPEC
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid or inconsistent flowspec was found in the QOS structure.
|
||||
//
|
||||
wsa_qos_eflowspec = 11017
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EPROVSPECBUF
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Invalid QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_eprovspecbuf = 11018
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EFILTERSTYLE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid QOS filter style was used.
|
||||
//
|
||||
wsa_qos_efilterstyle = 11019
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EFILTERTYPE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid QOS filter type was used.
|
||||
//
|
||||
wsa_qos_efiltertype = 11020
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EFILTERCOUNT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An incorrect number of QOS FILTERSPECs were specified in the FLOWDESCRIPTOR.
|
||||
//
|
||||
wsa_qos_efiltercount = 11021
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EOBJLENGTH
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An object with an invalid ObjectLength field was specified in the QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_eobjlength = 11022
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EFLOWCOUNT
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An incorrect number of flow descriptors was specified in the QOS structure.
|
||||
//
|
||||
wsa_qos_eflowcount = 11023
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EUNKOWNPSOBJ
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An unrecognized object was found in the QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_eunkownpsobj = 11024
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EPOLICYOBJ
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid policy object was found in the QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_epolicyobj = 11025
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EFLOWDESC
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid QOS flow descriptor was found in the flow descriptor list.
|
||||
//
|
||||
wsa_qos_eflowdesc = 11026
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EPSFLOWSPEC
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid or inconsistent flowspec was found in the QOS provider specific buffer.
|
||||
//
|
||||
wsa_qos_epsflowspec = 11027
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_EPSFILTERSPEC
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid FILTERSPEC was found in the QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_epsfilterspec = 11028
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_ESDMODEOBJ
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid shape discard mode object was found in the QOS provider specific buffer.
|
||||
//
|
||||
wsa_qos_esdmodeobj = 11029
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_ESHAPERATEOBJ
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// An invalid shaping rate object was found in the QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_eshaperateobj = 11030
|
||||
|
||||
//
|
||||
// MessageId: WSA_QOS_RESERVED_PETYPE
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// A reserved policy element was found in the QOS provider-specific buffer.
|
||||
//
|
||||
wsa_qos_reserved_petype = 11031
|
||||
|
||||
//
|
||||
// MessageId: WSA_SECURE_HOST_NOT_FOUND
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// No such host is known securely.
|
||||
//
|
||||
wsa_secure_host_not_found = 11032
|
||||
|
||||
//
|
||||
// MessageId: WSA_IPSEC_NAME_POLICY_ERROR
|
||||
//
|
||||
// MessageText:
|
||||
//
|
||||
// Name based IPSEC policy could not be added.
|
||||
//
|
||||
wsa_ipsec_name_policy_error = 11033
|
||||
}
|
||||
|
||||
// wsa_error casts an int to its WsaError value
|
||||
pub fn wsa_error(code int) WsaError {
|
||||
return WsaError(code)
|
||||
}
|
||||
|
||||
const (
|
||||
error_ewouldblock = WsaError.wsaewouldblock
|
||||
)
|
||||
|
||||
// Link to Winsock library
|
||||
#flag -lws2_32
|
||||
#include <winsock2.h>
|
||||
#include <Ws2tcpip.h>
|
||||
|
||||
// Constants that windows needs
|
||||
const (
|
||||
fionbio = C.FIONBIO
|
||||
msg_nosignal = 0
|
||||
wsa_v22 = 0x202 // C.MAKEWORD(2, 2)
|
||||
)
|
||||
|
||||
// Error code returns the last socket error
|
||||
fn error_code() int {
|
||||
return C.WSAGetLastError()
|
||||
}
|
||||
|
||||
struct C.WSAData {
|
||||
mut:
|
||||
wVersion u16
|
||||
wHighVersion u16
|
||||
szDescription [257]byte
|
||||
szSystemStatus [129]byte
|
||||
iMaxSockets u16
|
||||
iMaxUdpDg u16
|
||||
lpVendorInfo byteptr
|
||||
}
|
||||
|
||||
fn init() {
|
||||
mut wsadata := C.WSAData{}
|
||||
res := C.WSAStartup(wsa_v22, &wsadata)
|
||||
if res != 0 {
|
||||
panic('socket: WSAStartup failed')
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import net
|
||||
import encoding.base64
|
||||
import strings
|
||||
import time
|
||||
import io
|
||||
|
||||
const (
|
||||
recv_size = 128
|
||||
@@ -29,7 +30,8 @@ pub enum BodyType {
|
||||
|
||||
pub struct Client {
|
||||
mut:
|
||||
socket net.Socket
|
||||
conn net.TcpConn
|
||||
reader io.BufferedReader
|
||||
pub:
|
||||
server string
|
||||
port int = 25
|
||||
@@ -62,8 +64,10 @@ pub fn new_client(config Client) ?Client {
|
||||
pub fn (mut c Client) reconnect() ? {
|
||||
if c.is_open { return error('Already connected to server') }
|
||||
|
||||
socket := net.dial(c.server, c.port) or { return error('Connecting to server failed') }
|
||||
c.socket = socket
|
||||
conn := net.dial_tcp('$c.server:$c.port') or { return error('Connecting to server failed') }
|
||||
c.conn = conn
|
||||
|
||||
c.reader = io.new_buffered_reader(reader: io.make_reader(c.conn))
|
||||
|
||||
c.expect_reply(.ready) or { return error('Received invalid response from server') }
|
||||
c.send_ehlo() or { return error('Sending EHLO packet failed') }
|
||||
@@ -85,35 +89,21 @@ pub fn (c Client) send(config Mail) ? {
|
||||
pub fn (mut c Client) quit() ? {
|
||||
c.send_str('QUIT\r\n')
|
||||
c.expect_reply(.close)
|
||||
c.socket.close()?
|
||||
c.conn.close()?
|
||||
c.is_open = false
|
||||
}
|
||||
|
||||
// expect_reply checks if the SMTP server replied with the expected reply code
|
||||
fn (c Client) expect_reply(expected ReplyCode) ? {
|
||||
bytes, len := c.socket.recv(recv_size)
|
||||
bytes := io.read_all(c.conn)?
|
||||
|
||||
str := tos(bytes, len).trim_space()
|
||||
str := bytes.bytestr().trim_space()
|
||||
$if smtp_debug? {
|
||||
eprintln('\n\n[RECV START]')
|
||||
eprintln('\n\n[RECV]')
|
||||
eprint(str)
|
||||
}
|
||||
|
||||
// Read remaining data in the socket
|
||||
if len >= recv_size {
|
||||
for {
|
||||
tbytes, tlen := c.socket.recv(recv_size)
|
||||
str2 := tos(tbytes, tlen)
|
||||
$if smtp_debug? { eprint(str2) }
|
||||
if tlen < recv_size { break }
|
||||
}
|
||||
}
|
||||
|
||||
$if smtp_debug? {
|
||||
eprintln('\n[RECV END]')
|
||||
}
|
||||
|
||||
if len >= 3 {
|
||||
if str.len >= 3 {
|
||||
status := str[..3].int()
|
||||
if status != expected {
|
||||
return error('Received unexpected status code $status, expecting $expected')
|
||||
@@ -128,7 +118,7 @@ fn (c Client) send_str(s string) ? {
|
||||
eprint(s.trim_space())
|
||||
eprintln('\n[SEND END]')
|
||||
}
|
||||
c.socket.send_string(s)?
|
||||
c.conn.write(s.bytes())?
|
||||
}
|
||||
|
||||
[inline]
|
||||
|
||||
@@ -1,392 +0,0 @@
|
||||
module net
|
||||
|
||||
import os
|
||||
|
||||
pub struct Socket {
|
||||
pub:
|
||||
sockfd int
|
||||
family int
|
||||
typ int
|
||||
proto int
|
||||
pub mut:
|
||||
max_single_send_size int = 64000
|
||||
}
|
||||
|
||||
struct C.in_addr {
|
||||
mut:
|
||||
s_addr int
|
||||
}
|
||||
|
||||
struct C.sockaddr {
|
||||
}
|
||||
|
||||
struct C.sockaddr_in {
|
||||
mut:
|
||||
sin_family int
|
||||
sin_port int
|
||||
sin_addr C.in_addr
|
||||
}
|
||||
|
||||
struct C.addrinfo {
|
||||
mut:
|
||||
ai_family int
|
||||
ai_socktype int
|
||||
ai_flags int
|
||||
ai_protocol int
|
||||
ai_addrlen int
|
||||
ai_addr voidptr
|
||||
ai_canonname voidptr
|
||||
ai_next voidptr
|
||||
}
|
||||
|
||||
struct C.sockaddr_storage {
|
||||
}
|
||||
|
||||
fn C.socket() int
|
||||
|
||||
fn C.setsockopt() int
|
||||
|
||||
fn C.htonl() int
|
||||
|
||||
fn C.htons() int
|
||||
|
||||
fn C.bind() int
|
||||
|
||||
fn C.listen() int
|
||||
|
||||
fn C.accept() int
|
||||
|
||||
fn C.getaddrinfo() int
|
||||
|
||||
fn C.connect() int
|
||||
|
||||
fn C.send() int
|
||||
|
||||
fn C.recv() int
|
||||
|
||||
fn C.shutdown() int
|
||||
|
||||
fn C.ntohs() int
|
||||
|
||||
fn C.getsockname() int
|
||||
|
||||
fn C.inet_ntop(af int, src voidptr, dst charptr, dst_size int) charptr
|
||||
|
||||
fn C.getpeername(sockfd int, addr &C.sockaddr_in, addrsize &int) int
|
||||
|
||||
// create socket
|
||||
pub fn new_socket(family int, typ int, proto int) ?Socket {
|
||||
sockfd := C.socket(family, typ, proto)
|
||||
one := 1
|
||||
// This is needed so that there are no problems with reusing the
|
||||
// same port after the application exits.
|
||||
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(voidptr))
|
||||
if sockfd == -1 {
|
||||
return error('net.socket: failed')
|
||||
}
|
||||
s := Socket{
|
||||
sockfd: sockfd
|
||||
family: family
|
||||
typ: typ
|
||||
proto: proto
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn socket_udp() ?Socket {
|
||||
return new_socket(C.AF_INET, C.SOCK_DGRAM, C.IPPROTO_UDP)
|
||||
}
|
||||
|
||||
// set socket options
|
||||
pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
|
||||
res := C.setsockopt(s.sockfd, level, optname, optvalue, sizeof(int))
|
||||
if res < 0 {
|
||||
return error('net.setsocketopt: failed with $res')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// bind socket to port
|
||||
pub fn (s Socket) bind(port int) ?int {
|
||||
mut addr := C.sockaddr_in{}
|
||||
addr.sin_family = s.family
|
||||
addr.sin_port = C.htons(port)
|
||||
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
|
||||
size := 16 // sizeof(C.sockaddr_in)
|
||||
tmp := voidptr(&addr)
|
||||
skaddr := &C.sockaddr(tmp)
|
||||
res := C.bind(s.sockfd, skaddr, size)
|
||||
if res < 0 {
|
||||
return error('net.bind: failed with $res')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// put socket into passive mode and wait to receive
|
||||
pub fn (s Socket) listen() ?int {
|
||||
backlog := 128
|
||||
res := C.listen(s.sockfd, backlog)
|
||||
if res < 0 {
|
||||
return error('net.listen: failed with $res')
|
||||
}
|
||||
$if debug {
|
||||
println('listen res = $res')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// put socket into passive mode with user specified backlog and wait to receive
|
||||
pub fn (s Socket) listen_backlog(backlog int) ?int {
|
||||
mut n := 0
|
||||
if backlog > 0 {
|
||||
n = backlog
|
||||
}
|
||||
res := C.listen(s.sockfd, n)
|
||||
if res < 0 {
|
||||
return error('net.listen_backlog: failed with $res')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// helper method to create, bind, and listen given port number
|
||||
pub fn listen(port int) ?Socket {
|
||||
$if debug {
|
||||
println('net.listen($port)')
|
||||
}
|
||||
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) ?
|
||||
s.bind(port) ?
|
||||
s.listen() ?
|
||||
return s
|
||||
}
|
||||
|
||||
// accept first connection request from socket queue
|
||||
pub fn (s Socket) accept() ?Socket {
|
||||
$if debug {
|
||||
println('accept()')
|
||||
}
|
||||
addr := C.sockaddr{}
|
||||
size := sizeof(addr)
|
||||
sockfd := C.accept(s.sockfd, &addr, &size)
|
||||
if sockfd < 0 {
|
||||
return error('net.accept: failed with $sockfd')
|
||||
}
|
||||
c := Socket{
|
||||
sockfd: sockfd
|
||||
family: s.family
|
||||
typ: s.typ
|
||||
proto: s.proto
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
pub fn (s Socket) peer_ip() ?string {
|
||||
buf := [44]byte{}
|
||||
peeraddr := C.sockaddr_in{}
|
||||
speeraddr := sizeof(peeraddr)
|
||||
ok := C.getpeername(s.sockfd, &C.sockaddr(&peeraddr), &speeraddr)
|
||||
if ok == -1 {
|
||||
return error('net.peer_ip: getpeername failed')
|
||||
}
|
||||
cstr := C.inet_ntop(C.AF_INET, &peeraddr.sin_addr, buf, sizeof(buf))
|
||||
if cstr == 0 {
|
||||
return error('net.peer_ip: inet_ntop failed')
|
||||
}
|
||||
res := cstring_to_vstring(cstr)
|
||||
return res
|
||||
}
|
||||
|
||||
// connect to given addrress and port
|
||||
pub fn (s Socket) connect(address string, port int) ?int {
|
||||
mut hints := C.addrinfo{}
|
||||
hints.ai_family = s.family
|
||||
hints.ai_socktype = s.typ
|
||||
hints.ai_flags = C.AI_PASSIVE
|
||||
hints.ai_protocol = s.proto
|
||||
hints.ai_addrlen = 0
|
||||
hints.ai_canonname = C.NULL
|
||||
hints.ai_addr = C.NULL
|
||||
hints.ai_next = C.NULL
|
||||
info := &C.addrinfo(0)
|
||||
sport := '$port'
|
||||
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
|
||||
if info_res != 0 {
|
||||
error_message := os.get_error_msg(error_code())
|
||||
return error('net.connect: getaddrinfo failed "$error_message"')
|
||||
}
|
||||
res := C.connect(s.sockfd, info.ai_addr, info.ai_addrlen)
|
||||
if res < 0 {
|
||||
error_message := os.get_error_msg(error_code())
|
||||
return error('net.connect: connect failed "$error_message"')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// helper method to create socket and connect
|
||||
pub fn dial(address string, port int) ?Socket {
|
||||
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) ?
|
||||
s.connect(address, port) ?
|
||||
return s
|
||||
}
|
||||
|
||||
// send data to socket (when you have a memory buffer)
|
||||
pub fn (s Socket) send(buf byteptr, len int) ?int {
|
||||
mut dptr := buf
|
||||
mut dlen := len
|
||||
for {
|
||||
send_size := if dlen > s.max_single_send_size { s.max_single_send_size } else { dlen }
|
||||
sbytes := C.send(s.sockfd, dptr, send_size, msg_nosignal)
|
||||
if sbytes < 0 {
|
||||
return error('net.send: failed with $sbytes')
|
||||
}
|
||||
dlen -= sbytes
|
||||
if dlen <= 0 {
|
||||
break
|
||||
}
|
||||
unsafe {
|
||||
dptr += sbytes
|
||||
}
|
||||
}
|
||||
return len
|
||||
}
|
||||
|
||||
// send string data to socket (when you have a v string)
|
||||
pub fn (s Socket) send_string(sdata string) ?int {
|
||||
return s.send(sdata.str, sdata.len)
|
||||
}
|
||||
|
||||
// receive string data from socket. NB: you are responsible for freeing the returned byteptr
|
||||
pub fn (s Socket) recv(bufsize int) (byteptr, int) {
|
||||
mut buf := byteptr(0)
|
||||
unsafe {
|
||||
buf = malloc(bufsize)
|
||||
}
|
||||
res := C.recv(s.sockfd, buf, bufsize, 0)
|
||||
return buf, res
|
||||
}
|
||||
|
||||
// TODO: remove cread/2 and crecv/2 when the Go net interface is done
|
||||
pub fn (s Socket) cread(buffer byteptr, buffersize int) int {
|
||||
res := C.read(s.sockfd, buffer, buffersize)
|
||||
return res
|
||||
}
|
||||
|
||||
// Receive a message from the socket, and place it in a preallocated buffer buf,
|
||||
// with maximum message size bufsize. Returns the length of the received message.
|
||||
pub fn (s Socket) crecv(buffer voidptr, buffersize int) int {
|
||||
res := C.recv(s.sockfd, byteptr(buffer), buffersize, 0)
|
||||
return res
|
||||
}
|
||||
|
||||
// shutdown and close socket
|
||||
pub fn (s Socket) close() ?int {
|
||||
mut shutdown_res := 0
|
||||
$if windows {
|
||||
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
|
||||
} $else {
|
||||
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
|
||||
}
|
||||
// TODO: should shutdown throw an error? close will
|
||||
// continue even if shutdown failed
|
||||
// if shutdown_res < 0 {
|
||||
// return error('net.close: shutdown failed with $shutdown_res')
|
||||
// }
|
||||
mut res := 0
|
||||
$if windows {
|
||||
res = C.closesocket(s.sockfd)
|
||||
} $else {
|
||||
res = C.close(s.sockfd)
|
||||
}
|
||||
if res < 0 {
|
||||
return error('net.close: failed with $res')
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
pub const (
|
||||
crlf = '\r\n'
|
||||
max_read = 400
|
||||
msg_peek = 0x02
|
||||
)
|
||||
|
||||
// write - write a string with CRLF after it over the socket s
|
||||
pub fn (s Socket) write(str string) ?int {
|
||||
line := '$str$crlf'
|
||||
res := C.send(s.sockfd, line.str, line.len, msg_nosignal)
|
||||
if res < 0 {
|
||||
return error('net.write: failed with $res')
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// read_line - retrieves a line from the socket s (i.e. a string ended with \n)
|
||||
pub fn (s Socket) read_line() string {
|
||||
mut buf := [max_read]byte{} // where C.recv will store the network data
|
||||
mut res := '' // The final result, including the ending \n.
|
||||
for {
|
||||
mut line := '' // The current line. Can be a partial without \n in it.
|
||||
n := C.recv(s.sockfd, buf, max_read - 1, msg_peek)
|
||||
if n == -1 {
|
||||
return res
|
||||
}
|
||||
if n == 0 {
|
||||
return res
|
||||
}
|
||||
buf[n] = `\0`
|
||||
mut eol_idx := -1
|
||||
for i in 0 .. n {
|
||||
if int(buf[i]) == `\n` {
|
||||
eol_idx = i
|
||||
// Ensure that tos_clone(buf) later,
|
||||
// will return *only* the first line (including \n),
|
||||
// and ignore the rest
|
||||
buf[i + 1] = `\0`
|
||||
break
|
||||
}
|
||||
}
|
||||
bufbp := byteptr(buf)
|
||||
line = tos_clone(bufbp)
|
||||
if eol_idx > 0 {
|
||||
// At this point, we are sure that recv returned valid data,
|
||||
// that contains *at least* one line.
|
||||
// Ensure that the block till the first \n (including it)
|
||||
// is removed from the socket's receive queue, so that it does
|
||||
// not get read again.
|
||||
C.recv(s.sockfd, buf, eol_idx + 1, 0)
|
||||
res += line
|
||||
break
|
||||
}
|
||||
// recv returned a buffer without \n in it .
|
||||
C.recv(s.sockfd, buf, n, 0)
|
||||
res += line
|
||||
res += crlf
|
||||
break
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// TODO
|
||||
pub fn (s Socket) read_all() string {
|
||||
mut buf := [max_read]byte{} // where C.recv will store the network data
|
||||
mut res := '' // The final result, including the ending \n.
|
||||
for {
|
||||
n := C.recv(s.sockfd, buf, max_read - 1, 0)
|
||||
if n == -1 {
|
||||
return res
|
||||
}
|
||||
if n == 0 {
|
||||
return res
|
||||
}
|
||||
bufbp := byteptr(buf)
|
||||
res += tos_clone(bufbp)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
pub fn (s Socket) get_port() int {
|
||||
mut addr := C.sockaddr_in{}
|
||||
size := 16 // sizeof(sockaddr_in)
|
||||
tmp := voidptr(&addr)
|
||||
skaddr := &C.sockaddr(tmp)
|
||||
C.getsockname(s.sockfd, skaddr, &size)
|
||||
return C.ntohs(addr.sin_port)
|
||||
}
|
||||
55
vlib/net/socket_options.c.v
Normal file
55
vlib/net/socket_options.c.v
Normal file
@@ -0,0 +1,55 @@
|
||||
module net
|
||||
|
||||
enum SocketOption {
|
||||
// TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it
|
||||
// and there is no easy way to define it
|
||||
|
||||
broadcast = C.SO_BROADCAST
|
||||
debug = C.SO_DEBUG
|
||||
dont_route = C.SO_DONTROUTE
|
||||
error = C.SO_ERROR
|
||||
keep_alive = C.SO_KEEPALIVE
|
||||
linger = C.SO_LINGER
|
||||
oob_inline = C.SO_OOBINLINE
|
||||
|
||||
reuse_addr = C.SO_REUSEADDR
|
||||
|
||||
recieve_buf_size = C.SO_RCVBUF
|
||||
recieve_low_size = C.SO_RCVLOWAT
|
||||
recieve_timeout = C.SO_RCVTIMEO
|
||||
|
||||
send_buf_size = C.SO_SNDBUF
|
||||
send_low_size = C.SO_SNDLOWAT
|
||||
send_timeout = C.SO_SNDTIMEO
|
||||
|
||||
socket_type = C.SO_TYPE
|
||||
}
|
||||
|
||||
const (
|
||||
opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline]
|
||||
opts_int = [
|
||||
.recieve_buf_size,
|
||||
.recieve_low_size,
|
||||
.recieve_timeout,
|
||||
|
||||
.send_buf_size,
|
||||
.send_low_size,
|
||||
.send_timeout,
|
||||
]
|
||||
|
||||
opts_can_set = [
|
||||
SocketOption.broadcast,
|
||||
.debug,
|
||||
.dont_route,
|
||||
.keep_alive,
|
||||
.linger,
|
||||
.oob_inline,
|
||||
.recieve_buf_size,
|
||||
.recieve_low_size,
|
||||
.recieve_timeout,
|
||||
|
||||
.send_buf_size,
|
||||
.send_low_size,
|
||||
.send_timeout,
|
||||
]
|
||||
)
|
||||
@@ -1,106 +0,0 @@
|
||||
import net
|
||||
|
||||
fn setup() (net.Socket, net.Socket, net.Socket) {
|
||||
server := net.listen(0) or {
|
||||
panic(err)
|
||||
}
|
||||
server_port := server.get_port()
|
||||
client := net.dial('127.0.0.1', server_port) or {
|
||||
panic(err)
|
||||
}
|
||||
socket := server.accept() or {
|
||||
panic(err)
|
||||
}
|
||||
$if debug_peer_ip ? {
|
||||
ip := socket.peer_ip() or {
|
||||
'$err'
|
||||
}
|
||||
eprintln('socket peer_ip: $ip')
|
||||
}
|
||||
return server, client, socket
|
||||
}
|
||||
|
||||
fn cleanup(server &net.Socket, client &net.Socket, socket &net.Socket) {
|
||||
server.close() or { }
|
||||
client.close() or { }
|
||||
socket.close() or { }
|
||||
}
|
||||
|
||||
fn test_socket() {
|
||||
server, client, socket := setup()
|
||||
defer {
|
||||
cleanup(server, client, socket)
|
||||
}
|
||||
message := 'Hello World'
|
||||
socket.send(message.str, message.len) or {
|
||||
assert false
|
||||
}
|
||||
$if debug {
|
||||
println('message send: $message')
|
||||
}
|
||||
$if debug {
|
||||
println('send socket: $socket.sockfd')
|
||||
}
|
||||
bytes, blen := client.recv(1024)
|
||||
received := tos(bytes, blen)
|
||||
$if debug {
|
||||
println('message received: $received')
|
||||
}
|
||||
$if debug {
|
||||
println('client: $client.sockfd')
|
||||
}
|
||||
assert message == received
|
||||
}
|
||||
|
||||
fn test_socket_read_line() {
|
||||
server, client, socket := setup()
|
||||
defer {
|
||||
cleanup(server, client, socket)
|
||||
}
|
||||
message1, message2 := 'message1', 'message2'
|
||||
message := '$message1\n$message2'
|
||||
socket.write(message) or {
|
||||
assert false
|
||||
}
|
||||
line1, line2 := client.read_line(), client.read_line()
|
||||
assert line1 != message1
|
||||
assert line1.trim_space() == message1
|
||||
assert line2 != message2
|
||||
assert line2.trim_space() == message2
|
||||
}
|
||||
|
||||
fn test_socket_write() {
|
||||
server, client, socket := setup()
|
||||
defer {
|
||||
cleanup(server, client, socket)
|
||||
}
|
||||
message1 := 'a message 1'
|
||||
socket.write(message1) or {
|
||||
assert false
|
||||
}
|
||||
line1 := client.read_line()
|
||||
assert line1 != message1
|
||||
assert line1.trim_space() == message1
|
||||
}
|
||||
|
||||
fn test_socket_write_fail_without_panic() {
|
||||
server, client, socket := setup()
|
||||
defer {
|
||||
cleanup(server, client, socket)
|
||||
}
|
||||
message2 := 'a message 2'
|
||||
// ensure that socket.write (i.e. done on the server side)
|
||||
// continues to work, even when the client side has been disconnected
|
||||
// this test is important for a stable long standing server
|
||||
client.close() or { }
|
||||
$if solaris {
|
||||
return
|
||||
}
|
||||
// TODO: fix segfaulting on Solaris
|
||||
for i := 0; i < 3; i++ {
|
||||
socket.write(message2) or {
|
||||
println('write to a socket without a recipient should produce an option fail: $err | $message2')
|
||||
assert true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import net
|
||||
|
||||
fn start_socket_udp_server() {
|
||||
bufsize := 1024
|
||||
bytes := [1024]byte{}
|
||||
s := net.socket_udp() or { panic(err) }
|
||||
s.bind( 9876 ) or { panic(err) }
|
||||
println('Waiting for udp packets:')
|
||||
for {
|
||||
res := s.crecv(bytes, bufsize)
|
||||
if res < 0 { break }
|
||||
print('Received $res bytes: ' + tos(bytes, res))
|
||||
}
|
||||
}
|
||||
|
||||
fn test_udp_server() {
|
||||
// start_socket_udp_server()
|
||||
}
|
||||
365
vlib/net/tcp.v
Normal file
365
vlib/net/tcp.v
Normal file
@@ -0,0 +1,365 @@
|
||||
module net
|
||||
|
||||
import time
|
||||
|
||||
pub struct TcpConn {
|
||||
pub:
|
||||
sock TcpSocket
|
||||
|
||||
mut:
|
||||
write_deadline time.Time
|
||||
read_deadline time.Time
|
||||
|
||||
read_timeout time.Duration
|
||||
write_timeout time.Duration
|
||||
}
|
||||
|
||||
pub fn dial_tcp(address string) ?TcpConn {
|
||||
s := new_tcp_socket()?
|
||||
s.connect(address)?
|
||||
|
||||
return TcpConn {
|
||||
sock: s
|
||||
|
||||
read_timeout: 30 * time.second
|
||||
write_timeout: 30 * time.second
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) close() ? {
|
||||
c.sock.close()?
|
||||
return none
|
||||
}
|
||||
|
||||
// write_ptr blocks and attempts to write all data
|
||||
pub fn (c TcpConn) write_ptr(b byteptr, len int) ? {
|
||||
unsafe {
|
||||
mut ptr_base := byteptr(b)
|
||||
mut total_sent := 0
|
||||
|
||||
for total_sent < len {
|
||||
ptr := ptr_base + total_sent
|
||||
remaining := len - total_sent
|
||||
mut sent := C.send(c.sock.handle, ptr, remaining, msg_nosignal)
|
||||
|
||||
if sent < 0 {
|
||||
code := error_code()
|
||||
match code {
|
||||
error_ewouldblock {
|
||||
c.wait_for_write()
|
||||
continue
|
||||
}
|
||||
else {
|
||||
wrap_error(code)?
|
||||
}
|
||||
}
|
||||
}
|
||||
total_sent += sent
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// write blocks and attempts to write all data
|
||||
pub fn (c TcpConn) write(bytes []byte) ? {
|
||||
return c.write_ptr(bytes.data, bytes.len)
|
||||
}
|
||||
|
||||
// write_str blocks and attempts to write all data
|
||||
pub fn (c TcpConn) write_str(s string) ? {
|
||||
return c.write_ptr(s.str, s.len)
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) read_ptr(buf_ptr byteptr, len int) ?int {
|
||||
mut res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))?
|
||||
|
||||
if res > 0 {
|
||||
return res
|
||||
}
|
||||
|
||||
code := error_code()
|
||||
match code {
|
||||
error_ewouldblock {
|
||||
c.wait_for_read()?
|
||||
res = wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))?
|
||||
return socket_error(res)
|
||||
}
|
||||
else {
|
||||
wrap_error(code)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) read(mut buf []byte) ?int {
|
||||
return c.read_ptr(buf.data, buf.len)
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) read_deadline() ?time.Time {
|
||||
if c.read_deadline.unix == 0 {
|
||||
return c.read_deadline
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) {
|
||||
c.read_deadline = deadline
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) write_deadline() ?time.Time {
|
||||
if c.write_deadline.unix == 0 {
|
||||
return c.write_deadline
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) {
|
||||
c.write_deadline = deadline
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) read_timeout() time.Duration {
|
||||
return c.read_timeout
|
||||
}
|
||||
|
||||
pub fn(mut c TcpConn) set_read_timeout(t time.Duration) {
|
||||
c.read_timeout = t
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) write_timeout() time.Duration {
|
||||
return c.write_timeout
|
||||
}
|
||||
|
||||
pub fn (mut c TcpConn) set_write_timeout(t time.Duration) {
|
||||
c.write_timeout = t
|
||||
}
|
||||
|
||||
[inline]
|
||||
pub fn (c TcpConn) wait_for_read() ? {
|
||||
return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
|
||||
}
|
||||
|
||||
[inline]
|
||||
pub fn (c TcpConn) wait_for_write() ? {
|
||||
return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout)
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) peer_addr() ?Addr {
|
||||
mut addr := C.sockaddr{}
|
||||
len := sizeof(C.sockaddr)
|
||||
|
||||
socket_error(C.getpeername(c.sock.handle, &addr, &len))?
|
||||
|
||||
return new_addr(addr)
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) peer_ip() ?string {
|
||||
buf := [44]byte{}
|
||||
peeraddr := C.sockaddr_in{}
|
||||
speeraddr := sizeof(peeraddr)
|
||||
socket_error(C.getpeername(c.sock.handle, &C.sockaddr(&peeraddr), &speeraddr))?
|
||||
cstr := C.inet_ntop(C.AF_INET, &peeraddr.sin_addr, buf, sizeof(buf))
|
||||
if cstr == 0 {
|
||||
return error('net.peer_ip: inet_ntop failed')
|
||||
}
|
||||
res := cstring_to_vstring(cstr)
|
||||
return res
|
||||
}
|
||||
|
||||
pub fn (c TcpConn) str() string {
|
||||
// TODO
|
||||
return 'TcpConn'
|
||||
}
|
||||
|
||||
pub struct TcpListener {
|
||||
sock TcpSocket
|
||||
|
||||
mut:
|
||||
accept_timeout time.Duration
|
||||
accept_deadline time.Time
|
||||
}
|
||||
|
||||
pub fn listen_tcp(port int) ?TcpListener {
|
||||
s := new_tcp_socket()?
|
||||
|
||||
validate_port(port)?
|
||||
|
||||
mut addr := C.sockaddr_in{}
|
||||
addr.sin_family = SocketFamily.inet
|
||||
addr.sin_port = C.htons(port)
|
||||
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
|
||||
size := sizeof(C.sockaddr_in)
|
||||
|
||||
// cast to the correct type
|
||||
sockaddr := &C.sockaddr(&addr)
|
||||
|
||||
socket_error(C.bind(s.handle, sockaddr, size))?
|
||||
socket_error(C.listen(s.handle, 128))?
|
||||
|
||||
return TcpListener {
|
||||
sock: s
|
||||
accept_deadline: no_deadline
|
||||
accept_timeout: infinite_timeout
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (l TcpListener) accept() ?TcpConn {
|
||||
addr := C.sockaddr_storage{}
|
||||
unsafe {
|
||||
C.memset(&addr, 0, sizeof(C.sockaddr_storage))
|
||||
}
|
||||
size := sizeof(C.sockaddr_storage)
|
||||
|
||||
// cast to correct type
|
||||
sock_addr := &C.sockaddr(&addr)
|
||||
mut new_handle := C.accept(l.sock.handle, sock_addr, &size)
|
||||
|
||||
if new_handle <= 0 {
|
||||
l.wait_for_accept()?
|
||||
|
||||
new_handle = C.accept(l.sock.handle, sock_addr, &size)
|
||||
|
||||
if new_handle == -1 || new_handle == 0 {
|
||||
return none
|
||||
}
|
||||
}
|
||||
|
||||
new_sock := tcp_socket_from_handle(new_handle)?
|
||||
|
||||
return TcpConn{
|
||||
sock: new_sock
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (c TcpListener) accept_deadline() ?time.Time {
|
||||
if c.accept_deadline.unix != 0 {
|
||||
return c.accept_deadline
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) {
|
||||
c.accept_deadline = deadline
|
||||
}
|
||||
|
||||
pub fn (c TcpListener) accept_timeout() time.Duration {
|
||||
return c.accept_timeout
|
||||
}
|
||||
|
||||
pub fn(mut c TcpListener) set_accept_timeout(t time.Duration) {
|
||||
c.accept_timeout = t
|
||||
}
|
||||
|
||||
pub fn (c TcpListener) wait_for_accept() ? {
|
||||
return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout)
|
||||
}
|
||||
|
||||
pub fn (c TcpListener) close() ? {
|
||||
c.sock.close()?
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (c TcpListener) address() ?Addr {
|
||||
return c.sock.address()
|
||||
}
|
||||
|
||||
struct TcpSocket {
|
||||
pub:
|
||||
handle int
|
||||
}
|
||||
|
||||
fn new_tcp_socket() ?TcpSocket {
|
||||
sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.tcp, 0))?
|
||||
s := TcpSocket {
|
||||
handle: sockfd
|
||||
}
|
||||
//s.set_option_bool(.reuse_addr, true)?
|
||||
s.set_option_int(.reuse_addr, 1)?
|
||||
$if windows {
|
||||
t := true
|
||||
socket_error(C.ioctlsocket(sockfd, fionbio, &t))?
|
||||
} $else {
|
||||
socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
fn tcp_socket_from_handle(sockfd int) ?TcpSocket {
|
||||
s := TcpSocket {
|
||||
handle: sockfd
|
||||
}
|
||||
//s.set_option_bool(.reuse_addr, true)?
|
||||
s.set_option_int(.reuse_addr, 1)?
|
||||
$if windows {
|
||||
t := true
|
||||
socket_error(C.ioctlsocket(sockfd, fionbio, &t))?
|
||||
} $else {
|
||||
socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn (s TcpSocket) set_option_bool(opt SocketOption, value bool) ? {
|
||||
// TODO reenable when this `in` operation works again
|
||||
// if opt !in opts_can_set {
|
||||
// return err_option_not_settable
|
||||
// }
|
||||
|
||||
// if opt !in opts_bool {
|
||||
// return err_option_wrong_type
|
||||
// }
|
||||
|
||||
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(bool)))?
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (s TcpSocket) set_option_int(opt SocketOption, value int) ? {
|
||||
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int)))?
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn (s TcpSocket) close() ? {
|
||||
return shutdown(s.handle)
|
||||
}
|
||||
|
||||
fn (s TcpSocket) @select(test Select, timeout time.Duration) ?bool {
|
||||
return @select(s.handle, test, timeout)
|
||||
}
|
||||
|
||||
const (
|
||||
connect_timeout = 5 * time.second
|
||||
)
|
||||
|
||||
fn (s TcpSocket) connect(a string) ? {
|
||||
addr := resolve_addr(a, .inet, .tcp)?
|
||||
|
||||
res := C.connect(s.handle, &addr.addr, addr.len)
|
||||
|
||||
if res == 0 {
|
||||
return none
|
||||
}
|
||||
|
||||
_ := error_code()
|
||||
|
||||
write_result := s.@select(.write, connect_timeout)?
|
||||
if write_result {
|
||||
// succeeded
|
||||
return none
|
||||
}
|
||||
except_result := s.@select(.except, connect_timeout)?
|
||||
if except_result {
|
||||
return err_connect_failed
|
||||
}
|
||||
// otherwise we timed out
|
||||
return err_connect_timed_out
|
||||
}
|
||||
|
||||
// address gets the address of a socket
|
||||
pub fn (s TcpSocket) address() ?Addr {
|
||||
mut addr := C.sockaddr_in{}
|
||||
size := sizeof(C.sockaddr_in)
|
||||
// cast to the correct type
|
||||
sockaddr := &C.sockaddr(&addr)
|
||||
C.getsockname(s.handle, sockaddr, &size)
|
||||
return new_addr(sockaddr)
|
||||
}
|
||||
71
vlib/net/tcp_test.v
Normal file
71
vlib/net/tcp_test.v
Normal file
@@ -0,0 +1,71 @@
|
||||
import net
|
||||
import time
|
||||
|
||||
const (
|
||||
test_port = 45123
|
||||
)
|
||||
|
||||
fn handle_conn(_c net.TcpConn) {
|
||||
mut c := _c
|
||||
// arbitrary timeouts to ensure that it doesnt
|
||||
// instantly throw its hands in the air and give up
|
||||
c.set_read_timeout(10 * time.second)
|
||||
c.set_write_timeout(10 * time.second)
|
||||
for {
|
||||
mut buf := []byte{len: 100, init: 0}
|
||||
read := c.read(mut buf) or {
|
||||
println('Server: connection dropped')
|
||||
return
|
||||
}
|
||||
c.write(buf[..read]) or {
|
||||
println('Server: connection dropped')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn echo_server(l net.TcpListener) ? {
|
||||
for {
|
||||
new_conn := l.accept() or {
|
||||
continue
|
||||
}
|
||||
go handle_conn(new_conn)
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn echo() ? {
|
||||
mut c := net.dial_tcp('127.0.0.1:$test_port')?
|
||||
defer {
|
||||
c.close() or { }
|
||||
}
|
||||
// arbitrary timeouts to ensure that it doesnt
|
||||
// instantly throw its hands in the air and give up
|
||||
c.set_read_timeout(10 * time.second)
|
||||
c.set_write_timeout(10 * time.second)
|
||||
data := 'Hello from vlib/net!'
|
||||
c.write_str(data)?
|
||||
mut buf := []byte{len: 4096}
|
||||
read := c.read(mut buf)?
|
||||
assert read == data.len
|
||||
for i := 0; i < read; i++ {
|
||||
assert buf[i] == data[i]
|
||||
}
|
||||
println('Got "$buf.bytestr()"')
|
||||
return none
|
||||
}
|
||||
|
||||
fn test_tcp() {
|
||||
l := net.listen_tcp(test_port) or {
|
||||
panic(err)
|
||||
}
|
||||
go echo_server(l)
|
||||
echo() or {
|
||||
panic(err)
|
||||
}
|
||||
l.close() or { }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_tcp()
|
||||
}
|
||||
241
vlib/net/udp.v
Normal file
241
vlib/net/udp.v
Normal file
@@ -0,0 +1,241 @@
|
||||
module net
|
||||
|
||||
import time
|
||||
|
||||
pub struct UdpConn {
|
||||
sock UdpSocket
|
||||
|
||||
mut:
|
||||
write_deadline time.Time
|
||||
read_deadline time.Time
|
||||
|
||||
read_timeout time.Duration
|
||||
write_timeout time.Duration
|
||||
}
|
||||
|
||||
pub fn dial_udp(laddr string, raddr string) ?UdpConn {
|
||||
// Dont have to do this when its fixed
|
||||
// this just allows us to store this `none` optional in a struct
|
||||
resolve_wrapper := fn(raddr string) ?Addr {
|
||||
x := resolve_addr(raddr, .inet, .udp) or { return none }
|
||||
return x
|
||||
}
|
||||
|
||||
local := resolve_addr(laddr, .inet, .udp)?
|
||||
sbase := new_udp_socket(local.port)?
|
||||
|
||||
sock := UdpSocket {
|
||||
handle: sbase.handle
|
||||
|
||||
l: local
|
||||
r: resolve_wrapper(raddr)
|
||||
}
|
||||
|
||||
return UdpConn {
|
||||
sock: sock
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) write_ptr(b byteptr, len int) ? {
|
||||
remote := c.sock.remote() or {
|
||||
return err_no_udp_remote
|
||||
}
|
||||
|
||||
return c.write_to_ptr(remote, b, len)
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) write(buf []byte) ? {
|
||||
return c.write_ptr(buf.data, buf.len)
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) write_str(s string) ? {
|
||||
return c.write_ptr(s.str, s.len)
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) write_to_ptr(addr Addr, b byteptr, len int) ? {
|
||||
res := C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len)
|
||||
|
||||
if res >= 0 {
|
||||
return none
|
||||
}
|
||||
|
||||
code := error_code()
|
||||
match code {
|
||||
error_ewouldblock {
|
||||
c.wait_for_write()?
|
||||
socket_error(C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len))?
|
||||
}
|
||||
else {
|
||||
wrap_error(code)?
|
||||
}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// write_to blocks and writes the buf to the remote addr specified
|
||||
pub fn (c UdpConn) write_to(addr Addr, buf []byte) ? {
|
||||
return c.write_to_ptr(addr, buf.data, buf.len)
|
||||
}
|
||||
|
||||
// write_to_string blocks and writes the buf to the remote addr specified
|
||||
pub fn (c UdpConn) write_to_string(addr Addr, s string) ? {
|
||||
return c.write_to_ptr(addr, s.str, s.len)
|
||||
}
|
||||
|
||||
// read reads from the socket into buf up to buf.len returning the number of bytes read
|
||||
pub fn (c UdpConn) read(mut buf []byte) ?(int, Addr) {
|
||||
mut addr_from := C.sockaddr{}
|
||||
len := sizeof(C.sockaddr)
|
||||
|
||||
mut res := wrap_read_result(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, &len))?
|
||||
|
||||
if res > 0 {
|
||||
addr := new_addr(addr_from)?
|
||||
return res, addr
|
||||
}
|
||||
|
||||
code := error_code()
|
||||
match code {
|
||||
error_ewouldblock {
|
||||
c.wait_for_read()?
|
||||
// same setup as in tcp
|
||||
res = wrap_read_result(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, &len))?
|
||||
res2 := socket_error(res)?
|
||||
|
||||
addr := new_addr(addr_from)?
|
||||
return res2, addr
|
||||
}
|
||||
else {
|
||||
wrap_error(code)?
|
||||
}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) read_deadline() ?time.Time {
|
||||
if c.read_deadline.unix == 0 {
|
||||
return c.read_deadline
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (mut c UdpConn) set_read_deadline(deadline time.Time) {
|
||||
c.read_deadline = deadline
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) write_deadline() ?time.Time {
|
||||
if c.write_deadline.unix == 0 {
|
||||
return c.write_deadline
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (mut c UdpConn) set_write_deadline(deadline time.Time) {
|
||||
c.write_deadline = deadline
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) read_timeout() time.Duration {
|
||||
return c.read_timeout
|
||||
}
|
||||
|
||||
pub fn(mut c UdpConn) set_read_timeout(t time.Duration) {
|
||||
c.read_timeout = t
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) write_timeout() time.Duration {
|
||||
return c.write_timeout
|
||||
}
|
||||
|
||||
pub fn (mut c UdpConn) set_write_timeout(t time.Duration) {
|
||||
c.write_timeout = t
|
||||
}
|
||||
|
||||
[inline]
|
||||
pub fn (c UdpConn) wait_for_read() ? {
|
||||
return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
|
||||
}
|
||||
|
||||
[inline]
|
||||
pub fn (c UdpConn) wait_for_write() ? {
|
||||
return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout)
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) str() string {
|
||||
// TODO
|
||||
return 'UdpConn'
|
||||
}
|
||||
|
||||
pub fn (c UdpConn) close() ? {
|
||||
return c.sock.close()
|
||||
}
|
||||
|
||||
pub fn listen_udp(port int) ?UdpConn {
|
||||
s := new_udp_socket(port)?
|
||||
|
||||
return UdpConn {
|
||||
sock: s
|
||||
}
|
||||
}
|
||||
|
||||
struct UdpSocket {
|
||||
handle int
|
||||
|
||||
l Addr
|
||||
r ?Addr
|
||||
}
|
||||
|
||||
fn new_udp_socket(local_port int) ?UdpSocket {
|
||||
sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.udp, 0))?
|
||||
s := UdpSocket {
|
||||
handle: sockfd
|
||||
}
|
||||
s.set_option_bool(.reuse_addr, true)?
|
||||
$if windows {
|
||||
t := true
|
||||
socket_error(C.ioctlsocket(sockfd, fionbio, &t))?
|
||||
} $else {
|
||||
socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK))
|
||||
}
|
||||
|
||||
// In UDP we always have to bind to a port
|
||||
validate_port(local_port)?
|
||||
|
||||
mut addr := C.sockaddr_in{}
|
||||
addr.sin_family = SocketFamily.inet
|
||||
addr.sin_port = C.htons(local_port)
|
||||
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
|
||||
size := sizeof(C.sockaddr_in)
|
||||
|
||||
// cast to the correct type
|
||||
sockaddr := &C.sockaddr(&addr)
|
||||
|
||||
socket_error(C.bind(s.handle, sockaddr, size))?
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn (s UdpSocket) remote() ?Addr {
|
||||
return s.r
|
||||
}
|
||||
|
||||
pub fn (s UdpSocket) set_option_bool(opt SocketOption, value bool) ? {
|
||||
// TODO reenable when this `in` operation works again
|
||||
// if opt !in opts_can_set {
|
||||
// return err_option_not_settable
|
||||
// }
|
||||
// if opt !in opts_bool {
|
||||
// return err_option_wrong_type
|
||||
// }
|
||||
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(bool)))?
|
||||
return none
|
||||
}
|
||||
|
||||
fn (s UdpSocket) close() ? {
|
||||
return shutdown(s.handle)
|
||||
}
|
||||
|
||||
fn (s UdpSocket) @select(test Select, timeout time.Duration) ?bool {
|
||||
return @select(s.handle, test, timeout)
|
||||
}
|
||||
74
vlib/net/udp_test.v
Normal file
74
vlib/net/udp_test.v
Normal file
@@ -0,0 +1,74 @@
|
||||
import net
|
||||
import time
|
||||
|
||||
fn echo_server(_c net.UdpConn) {
|
||||
mut c := _c
|
||||
// arbitrary timeouts to ensure that it doesnt
|
||||
// instantly throw its hands in the air and give up
|
||||
c.set_read_timeout(10 * time.second)
|
||||
c.set_write_timeout(10 * time.second)
|
||||
for {
|
||||
mut buf := []byte{ len: 100, init: 0 }
|
||||
read, addr := c.read(mut buf) or {
|
||||
continue
|
||||
}
|
||||
|
||||
c.write_to(addr, buf[..read]) or {
|
||||
println('Server: connection dropped')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn echo() ? {
|
||||
mut c := net.dial_udp('127.0.0.1:40003', '127.0.0.1:40001')?
|
||||
defer { c.close() or { } }
|
||||
|
||||
// arbitrary timeouts to ensure that it doesnt
|
||||
// instantly throw its hands in the air and give up
|
||||
c.set_read_timeout(10 * time.second)
|
||||
c.set_write_timeout(10 * time.second)
|
||||
|
||||
data := 'Hello from vlib/net!'
|
||||
|
||||
c.write_str(data)?
|
||||
|
||||
mut buf := []byte{ len: 100, init: 0 }
|
||||
read, addr := c.read(mut buf)?
|
||||
|
||||
assert read == data.len
|
||||
println('Got address $addr')
|
||||
// Can't test this here because loopback addresses
|
||||
// are mapped to other addresses
|
||||
// assert addr.str() == '127.0.0.1:30001'
|
||||
|
||||
for i := 0; i < read; i++ {
|
||||
assert buf[i] == data[i]
|
||||
}
|
||||
|
||||
println('Got "${buf.bytestr()}"')
|
||||
|
||||
c.close()?
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn test_udp() {
|
||||
l := net.listen_udp(40001) or {
|
||||
println(err)
|
||||
assert false
|
||||
panic('')
|
||||
}
|
||||
|
||||
go echo_server(l)
|
||||
echo() or {
|
||||
println(err)
|
||||
assert false
|
||||
}
|
||||
|
||||
l.close() or { }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_udp()
|
||||
}
|
||||
24
vlib/net/util.v
Normal file
24
vlib/net/util.v
Normal file
@@ -0,0 +1,24 @@
|
||||
module net
|
||||
|
||||
const (
|
||||
socket_max_port = u16(0xFFFF)
|
||||
)
|
||||
|
||||
// validate_port checks whether a port is valid
|
||||
// and returns the port or an error
|
||||
pub fn validate_port(port int) ?u16 {
|
||||
if port <= socket_max_port {
|
||||
return u16(port)
|
||||
} else {
|
||||
return err_port_out_of_range
|
||||
}
|
||||
}
|
||||
|
||||
// split address splits an address into its host name and its port
|
||||
pub fn split_address(addr string) ?(string, u16) {
|
||||
port := addr.all_after_last(':').int()
|
||||
address := addr.all_before_last(':')
|
||||
|
||||
p := validate_port(port)?
|
||||
return address, p
|
||||
}
|
||||
Reference in New Issue
Block a user