New FAMILUG

The PyMiers

Thursday, 31 August 2023

Truy cập website chỉ với TCP socket - Python, Perl, Bash

Thời đại ngày nay khi mọi ngôn ngữ lập trình đều có sẵn hay dễ dàng cài thư viện HTTP client, HTTP dần trở thành "hộp đen" ít người thọc vào táy máy. Chỉ có 1 TCP socket, liệu có kết nối HTTP được không? let's do it

CHÚ Ý: HTTP, không phải HTTPS

HTTP/1.1 protocol

Truy cập: HTTP/1.0 hay HTTP/1.1 là protocol (giao thức) dùng text, chỉ cần gửi các string đi là nhận được response.

Cấu trúc 1 HTTP/1.x request:

CRLF là Carriage Return and Line Feed \r\n

HTTP request format

Method Request-URI HTTP-Version CRLF
headers CRLF
message-body

Chạy HTTP server có sẵn ở python3

$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [31/Aug/2023 19:06:49] "GET / HTTP/1.1" 200 -

Gửi HTTP request bằng lệnh nc (netcat)

$ nc localhost 8000
GET / HTTP/1.1
header1=value

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.3
Date: Thu, 31 Aug 2023 12:06:49 GMT
Content-type: text/html; charset=utf-8
Content-Length: 6504
...

Gửi HTTP request bằng Python3 socket

Python3

import socket
# tạo 1 TCP socket (SOCK_STREAM)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Kết nối tới địa chỉ 127.0.0.1 port 8000
sock.connect(("127.0.0.1", 8000))
# Gửi HTTP request
sock.send(b"GET / HTTP/1.1\r\nheader=1\r\nbody\r\n\r\n")
# Nhận 1024 bytes kết quả, in 2 dòng đầu
print(sock.recv(1024).splitlines()[:2])
# [b'HTTP/1.0 200 OK', b'Server: SimpleHTTP/0.6 Python/3.11.3']
sock.close()

Gửi HTTP request bằng Perl

but why?

Khi thế giới đều chạy trên server, Python có ở mọi nơi, gần như mọi hệ điều hành UNIX/Linux thì khi thế giới chạy container (docker, K8S), Python không còn có ưu điểm này nữa. Container cắt giảm tối đa các phần mềm cài trên đó, thường không có Python, nhưng bất ngờ thay nhiều khi vẫn có perl hay bash.

# perl http.pl
use IO::Socket;
my $sock = new IO::Socket::INET (
                                 PeerAddr => '127.0.0.1',
                                 PeerPort => '8000',
                                 Proto => 'tcp',
                                );
die "Could not create socket: $!\n" unless $sock;
print $sock "GET / HTTP/1.1\r\n";
print $sock "Host: any.domain.org\r\n\r\n";
print while <$sock>;
close($sock);

Gửi HTTP request bằng bash

Khi không có perl, thì bash cũng được. CHÚ Ý: bash chứ không phải zsh hay dash.

Một tính năng có từ lâu, ít được biết tới, phụ thuộc vào option khi compile bash mà có hay không:

--enable-net-redirections This enables the special handling of filenames of the form /dev/tcp/host/port and /dev/udp/host/port when used in redirections (see Redirections).

$ exec 3<>/dev/tcp/127.0.0.1/8000
$ echo -e "GET / HTTP/1.1\r\n\r\n" >&3
$ cat <&3
Server: SimpleHTTP/0.6 Python/3.11.3
Date: Fri, 01 Sep 2023 02:54:38 GMT
Content-type: text/html; charset=utf-8
...

Xem thêm https://www.xmodulo.com/tcp-udp-socket-bash-shell.html

/dev/tcp/host/port If host is a valid hostname or Internet address, and port is an integer port number or service name, Bash attempts to open the corresponding TCP socket.

Tham khảo

Kết luận

HTTP 1 thật đơn giản, không như HTTP/2. Khi chỉ cần gửi HTTP request trong container, ai cần đến curl nữa?

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Friday, 18 August 2023

[Go] nil không là duy nhất, cap(map) = ?

Trong Python, None là giá trị duy nhất thể hiện sự "không có gì", sự "null", thì trong Go, nil lại là nhiều thứ khác nhau.

Go nil có kiểu

Mỗi giá trị nil có kiểu cụ thể.

package main

func main() {
    var s []int
    var m map[string]int

    println(s == nil)
    println(m == nil)
    // println(x == y) // Compile error: ./nil.go:9:15: invalid operation: s == m (mismatched types []int and map[string]int)
}

s và m đều bằng nil, nhưng không bằng nhau.

nil map không phải là vô dụng

var m map[string]int

Khi 1 map chưa được khởi tạo, nó có giá trị là nil. Map là kiểu reference, như pointer hay slices. nil map vẫn đọc giá trị bình thường (giá trị là zero value của kiểu int tức 0):

var m map[string]int
v := m["password"]
println(m)// 0x0
println(v)// 0

nil map không thêm được giá trị.

var m map[string]int
m["age"] = 8

Panic với nội dung

panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
    /home/hvn/me/familug.github.io/content/nil.go:5 +0x2e

panic: assignment to entry in nil map

nil map giống như empty map khi read, nhưng khác khi write.

var m map[string]int
fmt.Printf("nil: %v\n", m)
// nil: map[]

var e = map[string]int{}
fmt.Printf("empty: %v\n", e)
// empty: map[]

cap(map) bằng mấy?

Các kiểu reference trong Go phải allocation để cấp phát bộ nhớ sử dụng new hoặc make. Dùng make để alloc map hay slice.

It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use. A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is nil. For slices, maps, and channels, make initializes the internal data structure and prepares the value for use. https://go.dev/doc/effective_go#allocation_make

Một slice chứa 3 thông tin: pointer tới array phía dưới, length và capacity.

A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment). https://go.dev/blog/slices-intro

var nilSlice []int
fmt.Printf("%v len %d cap %d\n", nilSlice, len(nilSlice), cap(nilSlice))
// [] len 0 cap 0

var s = make([]int, 0)
fmt.Printf("%v len %d cap %d\n", s, len(s), cap(s))
// [] len 0 cap 0

var a = make([]int, 2, 10)
fmt.Printf("%v len %d cap %d\n", a, len(a), cap(a))
// [0 0] len 2 cap 10

Và dùng make với map:

var m = make(map[string]int)
fmt.Printf("%v len %d cap ?\n", m, len(m))

cap(m) bằng mấy?

fmt.Printf("%v len %d cap %d\n", m, len(m), cap(m))

./nil.go:7:49: invalid argument: m (variable of type map[string]int) for cap

Không dùng được cap với map. Tại sao? vì nó như thế và sẽ không thay đổi.

Sau 12+ năm dùng Go, tác giả bradfitz (golang team (2010-2020)) đã đề xuất thêm cap(map), nhưng đã bị từ chối https://github.com/golang/go/issues/52157.

Tham khảo

Kết luận

Go thật đơn giản, và đầy bất ngờ.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺