2016년 10월 11일 화요일

Solicit 소스 분석

https://github.com/mlalic/solicit

An HTTP/2 implementation in Rust.

- 개요
HttpConnect를 가지고 SimpleClient를 생성한다. 생성시 client preface를 서버에 보내고 또한 서버로부터 settings frame을 받는다. 이 작업이 끝나고 나면 request/get_response 함수의 호출을 통하여 서버로부터 응답을 받을 수 있다.
solicit는 request시 실제로 요청을 서버에 보낸다.(OkHttp나 hyper의 HTTP1.1구현의 경우는 request시 데이터만 만들어 놓고 요청을 서버에 보내지 않고 있다가 response를 받으려고 할 때 실제로 요청을 서버에 보낸다.)

- SimpleClient 만들기
SimpleClient::with_connector에 TlsConnector(HttpConnect를 구현하고 있음)를 넘겨줌. TlsConnector는 TCP를 연결(TcpStream::connect)하여 TcpStream을 생성하고 여기에 인증서를 합하여 SslStream을 생성함. 연결이 되고 나면 처음으로 SettingsFrame(client preface)를 서버에 보냄
첫 settings는 ENABLE_PUSH(0)만 서버에 보냄.

데이터 보내는데 사용하는 방법은 필요한 버퍼를 io::Cursor::new를 사용하여 할당하고, settings의 헤더와 데이터를 이 버퍼에 저장한 후 SslStream을 통해 이 버퍼의 내용을 보냄.
io::Cursor<Vec<u8>>는 FrameBuilder를 구현하고 있음.
frame header를 먼저 구성하여 버퍼에 쓰고 SettingsFrame에 설정한 HttpSetting을 하나씩 버퍼에 씀.
frame header는 (length, frame_type, flags, stream_id)의 FrameHeader로 되어 있음.


SslStream은 TransportStream을 구현하고 있음. 따라서, try_split를 통해 sender와 receiver를 구별.

ClientConnection을 HttpConnection과 DefaultSessionState을 가지고 생성.
결과적으로 SimpleClient는 ClientConnection과 host와 SslStream을 sender와 receiver로 나누어서 가지고 있게 됨.

처음으로 서버로부터 받아야 하는 패킷은 SettingsFrame임.
TransportStream은 SendFrame을 구현하고 있고 TransportReceiveFrame은 RecvFrame을 구현하고 있음.

- request 보내기

-- send_frame
io::Cursor를 사용해서 1024 크기의 버퍼를 할당한다.
보내려는 frame을 serialize하여 할당한 버퍼에 넣는다.
버퍼의 내용을 stream에 보낸다.

-- recv_frame
frame의 header만큼인 9byte를 read_exact를 사용해서 읽는다.
전체 frame을 위한 버퍼를 Vec::with_capacity를 사용해서 할당한다.
read_exact를 사용해서 frame의 나머지 부분을 읽는다.
읽은 raw frame으로 부터 frame type에 맞는 frame을 생성한다.

- stream 만들기
DefaultStream을 만든다. 처음 state는 StreamState::Open이다. body가 없는 경우 StreamState::HalfClosedLocal로 바뀐다.

- start_request
HalfClosedLocal이면 EndStream flag를 설정한다.
stream id와 Stream을 연결(HashMap)한다.
stream id는 client의 경우 1로 시작하고 2씩 증가한다.(홀수)
HttpConnectionSender를 통해 헤더의 key/value를 HPACK의 encoder를 통해 encoding하여 HeaderFrame을 만들어서 보낸다.
TODO: 멀티 헤더 지원 암함

- send_next_data

- simple.rs
http2를 사용할 때 사용자가 간단하게 사용할 수 있도록 해주는 SimpleClient가 있다. SimpleClient는 TransportStream을 sender와 receiver로 구별하고 내부에 가지고 있고 데이터를 보내고 받는데에 ClientConnection을 사용한다.
SimpleClient의 with_connector에 tcp연결을 하는 connector를 넘겨준다. 이의 구현은 ClearTextConnector와 TlsConnector가 있다.
서버에 요청은 request함수로 하고 get_response를 통해 응답을 받을 수 있다. 이에 대한 도움함수로 get과 post가 있다.

- client/mod.rs
암호화 없이 데이터를 주고 받는 CleartextConnector가 정의되어 있다. TCP 연결을 위해 HttpConnect를 구현한다.
CleartextConnector의 connect 구현을 보면 TcpStream::connect를 통해 TcpStream을 얻어오고 이를 ClientStream으로 감싸서 리턴한다. 이때 에러가 발생하면 io::Error를 그대로 리턴하지 않고 이를 CleartextConnectError로 감싸서 리턴한다.
서버와의 첫 연결시 client preface를 서버에 보내는 함수가 write_preface이다. 이 함수를 보면 Settings frame을 보낼 때 push를 disable해서 보낸다.(TODO: push 기능 지원 안함)
SimpleClient가 사용하는 ClientConnection이 정의되어 있다.
ClientConnection은 HttpConnection와 SessionState를 가지고 있다. start_request로 헤더를 보내고 send_next_data로 바디를 보낸다.
start_request는 stream id를 생성하고 HttpConnectionSender를 통해 헤더를 서버에 보낸다.
서버로부터 온 frame의 응답에 대한 처리를 위한 ClientSession이 있다.



- transport.rs
네트워크로 데이터를 보내기 위해 사용하는 TcpStream과 SslStream<TcpStream>가 TransportStream을 구현한다.
read_exact는 버퍼가 찰때까지 stream으로부터 데이터를 읽는다.
데이터를 보내기 위해 SendFrame을 구현하고 데이터를 받기 위해 ReceiveFrame을 구현한다.
여기서 데이터를 받을 때 직접 ReceiveFrame을 사용하지 않고 TransportReceiveFrame으로 한번 감싸서 사용한다. TransportReceiveFrame은 받은 frame을 가지고 있다.






댓글 없음:

댓글 쓰기

Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...