diff --git a/thirdparty/vschannel/vschannel.c b/thirdparty/vschannel/vschannel.c index 4cc5f02e56..3a484c7a83 100644 --- a/thirdparty/vschannel/vschannel.c +++ b/thirdparty/vschannel/vschannel.c @@ -134,7 +134,7 @@ void vschannel_init() { tls_ctx.creds_initialized = TRUE; } -INT request(CHAR *host, CHAR *req, CHAR *out) +INT request(INT iport, CHAR *host, CHAR *req, CHAR *out) { SecBuffer ExtraData; SECURITY_STATUS Status; @@ -147,6 +147,8 @@ INT request(CHAR *host, CHAR *req, CHAR *out) protocol = SP_PROT_TLS1_2_CLIENT; + port_number = iport; + // Connect to server. if(connect_to_server(host, port_number)) { printf("Error connecting to server\n"); diff --git a/thirdparty/vschannel/vschannel.h b/thirdparty/vschannel/vschannel.h index 782d40c37a..b990f86615 100644 --- a/thirdparty/vschannel/vschannel.h +++ b/thirdparty/vschannel/vschannel.h @@ -25,7 +25,7 @@ static void vschannel_init(); static void vschannel_cleanup(); -static INT request(CHAR *host, CHAR *req, CHAR *out); +static INT request(INT iport, CHAR *host, CHAR *req, CHAR *out); static SECURITY_STATUS https_make_request( CHAR *req, CHAR *out, int *length); diff --git a/vlib/http/backend_nix.v b/vlib/http/backend_nix.v index 706fa28106..4424632b4b 100644 --- a/vlib/http/backend_nix.v +++ b/vlib/http/backend_nix.v @@ -38,7 +38,7 @@ fn init_module() { //C.OPENSSL_config(0) } -fn ssl_do(method, host_name, path string) Response { +fn ssl_do(port int, method, host_name, path string) Response { //ssl_method := C.SSLv23_method() ssl_method := C.TLSv1_2_method() if isnil(method) { @@ -55,7 +55,7 @@ fn ssl_do(method, host_name, path string) Response { web := C.BIO_new_ssl_connect(ctx) if isnil(ctx) { } - addr := host_name + ':443' + addr := host_name + ':' + port.str() res = C.BIO_set_conn_hostname(web, addr.str) if res != 1 { } diff --git a/vlib/http/backend_win.v b/vlib/http/backend_win.v index 970b181ef9..0ec9f6fb9e 100644 --- a/vlib/http/backend_win.v +++ b/vlib/http/backend_win.v @@ -16,15 +16,15 @@ import net.urllib fn init_module() {} -fn ssl_do(method, host_name, path string) Response { +fn ssl_do(port int, method, host_name, path string) Response { C.vschannel_init() // TODO: joe-c // dynamically increase in vschannel.c if needed mut buff := malloc(44000) - + addr := host_name req := build_request_headers('', method, host_name, path) - length := int(C.request(host_name.str, req.str, buff)) - + length := int(C.request(port, addr.str, req.str, buff)) + C.vschannel_cleanup() return parse_response(string(buff, length)) } diff --git a/vlib/http/http.v b/vlib/http/http.v index 5e1f29f0b1..1c22c409c5 100644 --- a/vlib/http/http.v +++ b/vlib/http/http.v @@ -103,38 +103,45 @@ pub fn (req &Request) do() ?Response { for key, val in req.headers { //h := '$key: $val' } - url := urllib.parse(req.url) or { - return error('http.request.do: invalid URL $req.url') - // return Response{} //error('ff')} - } - is_ssl := url.scheme == 'https' - if !is_ssl { - return error('non https requests are not supported right now') - } - - // first request - mut p := url.path.trim_left('/') - mut u := if url.query().size > 0 { '/$p?${url.query().encode()}' } else { '/$p' } - mut resp := ssl_do(req.typ, url.hostname(), u) - // follow any redirects + url := urllib.parse(req.url) or { return error('http.request.do: invalid URL $req.url') } + mut rurl := url + mut resp := Response{} mut no_redirects := 0 - for resp.status_code in [301, 302, 303, 307, 308] { - if no_redirects == max_redirects { - return error('http.request.do: maximum number of redirects reached ($max_redirects)') - } - h_loc := resp.headers['Location'] - r_url := urllib.parse(h_loc) or { - return error('http.request.do: cannot follow redirect, location header has invalid url $h_loc') - } - p = r_url.path.trim_left('/') - u = if r_url.query().size > 0 { '/$p?${r_url.query().encode()}' } else { '/$p' } - resp = ssl_do(req.typ, r_url.hostname(), u) + for { + if no_redirects == max_redirects { return error('http.request.do: maximum number of redirects reached ($max_redirects)') } + qresp := method_and_url_to_response( req.typ, rurl ) or { return error(err) } + resp = qresp + if ! (resp.status_code in [301, 302, 303, 307, 308]) { break } + // follow any redirects + redirect_url := resp.headers['Location'] + qrurl := urllib.parse( redirect_url ) or { return error('http.request.do: invalid URL in redirect $redirect_url') } + rurl = qrurl no_redirects++ } - return resp } +fn method_and_url_to_response(method string, url net_dot_urllib.URL) ?Response { + host_name := url.hostname() + scheme := url.scheme + mut p := url.path.trim_left('/') + mut path := if url.query().size > 0 { '/$p?${url.query().encode()}' } else { '/$p' } + mut nport := url.port().int() + if nport == 0 { + if scheme == 'http' { nport = 80 } + if scheme == 'https' { nport = 443 } + } + //println('fetch $method, $scheme, $host_name, $nport, $path ') + if scheme == 'https' { + //println('ssl_do( $nport, $method, $host_name, $path )') + return ssl_do( nport, method, host_name, path ) + } else if scheme == 'http' { + //println('http_do( $nport, $method, $host_name, $path )') + return http_do( nport, method, host_name, path ) + } + return error('http.request.do: unsupported scheme: $scheme') +} + fn parse_response(resp string) Response { mut headers := map[string]string first_header := resp.all_before('\n') diff --git a/vlib/http/http_client.v b/vlib/http/http_client.v new file mode 100644 index 0000000000..9e3d9106b4 --- /dev/null +++ b/vlib/http/http_client.v @@ -0,0 +1,22 @@ +module http + +import net +import strings + +fn http_do(port int, method, host_name, path string) ?Response { + bufsize := 512 + rbuffer := [512]byte + mut sb := strings.new_builder(100) + s := build_request_headers('', method, host_name, path) + + client := net.dial( host_name, port) or { return error(err) } + client.send( s.str, s.len ) + for { + readbytes := client.crecv( rbuffer, bufsize ) + if readbytes < 0 { return error('http_do error reading response. readbytes: $readbytes') } + if readbytes == 0 { break } + sb.write( tos(rbuffer, readbytes) ) + } + client.close() + return parse_response(sb.str()) +} diff --git a/vlib/http/http_test.v b/vlib/http/http_test.v index efd5ebc955..68e3b81b54 100644 --- a/vlib/http/http_test.v +++ b/vlib/http/http_test.v @@ -1,5 +1,5 @@ import net.urllib -//import http +import http fn test_escape_unescape() { /* @@ -19,3 +19,32 @@ fn test_http_get() { */ } + +fn test_http_get_from_vlang_utc_now() { + urls := ['http://vlang.io/utc_now', 'https://vlang.io/utc_now'] + for url in urls { + println('Test getting current time from $url by http.get') + res := http.get(url) or { panic(err) } + assert 200 == res.status_code + assert res.text.len > 0 + assert res.text.int() > 1566403696 + println('Current time is: ${res.text.int()}') + } +} + +fn test_public_servers() { + urls := [ + 'http://github.com/robots.txt', + 'http://google.com/robots.txt', + 'http://yahoo.com/robots.txt', + 'https://github.com/robots.txt', + 'https://google.com/robots.txt', + 'https://yahoo.com/robots.txt', + ] + for url in urls { + println('Testing http.get on public url: $url ') + res := http.get( url ) or { panic(err) } + assert 200 == res.status_code + assert res.text.len > 0 + } +} diff --git a/vlib/net/socket.v b/vlib/net/socket.v index ed469ecc19..ecc6bd87f8 100644 --- a/vlib/net/socket.v +++ b/vlib/net/socket.v @@ -231,6 +231,14 @@ pub fn (s Socket) recv(bufsize int) byteptr { return buf } +// TODO: remove cread/2 and crecv/2 when the Go net interface is done +pub fn (s Socket) cread( buffer byteptr, buffersize int ) int { + return int( C.read(s.sockfd, buffer, buffersize) ) +} +pub fn (s Socket) crecv( buffer byteptr, buffersize int ) int { + return int( C.recv(s.sockfd, buffer, buffersize, 0) ) +} + // shutdown and close socket pub fn (s Socket) close() ?int { mut shutdown_res := 0