defmodule Http2StudyClient do require Logger @moduledoc """ http2study """ @doc "GET HTTP/2.0" def get domain, port do {:ok, sock} = :gen_tcp.connect domain, port, [:binary, {:packet, 0}] IO.inspect sock try do {:ok, data} = send_connection_preface sock IO.inspect data #{:ok, data} = establish_connection sock #IO.inspect data {:ok, data} = send_get_request sock IO.inspect data :ok = send_goaway sock after :ok = :gen_tcp.close sock end end def get_ssl domain, port do :ssl.start() {:ok, socket} = :gen_tcp.connect domain, port, [:binary, {:packet, 0}] options = [{:mode, :binary}, {:reuse_sessions, false}, {:packet, 0}] {:ok, sock} = :ssl.connect(socket, options) {:sslsocket, socket, pid} = sock {:gen_tcp, tcp_pid, :tls_connection, :undefined} = socket try do IO.inspect sock {:ok, data} = send_connection_preface_ssl sock IO.inspect data {:ok, data} = establish_connection sock IO.inspect data {:ok, data} = send_get_request sock IO.inspect data :ok = send_goaway sock after :ok = :ssl.close sock end end defp establish_connection sock do {:ok, data} = send_settings sock IO.inspect data unless data = build_frame(0x01, 0, 0, <<>>) do {:error, "Can't establish a connection."} else send_ack sock end end defp send_connection_preface_ssl sock do IO.puts "Sending SSL connection preface" :ssl.send sock, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" case do_receive_once(sock) do {:ok, data} -> if Regex.match?(~r/^HTTP\//, data) do {:error, "not supported."} else {:ok, data} end x -> x end end defp send_connection_preface sock do IO.puts "Sending connection preface" :gen_tcp.send sock, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" case do_receive_ssl(sock) do {:ok, data} -> if Regex.match?(~r/^HTTP\//, data) do {:error, "not supported."} else {:ok, data} end x -> x end end defp send_settings sock do IO.puts "Sending SETTINGS" :gen_tcp.send sock, build_frame(0x4, 0, 0, <<>>) do_receive_once sock end defp send_ack sock do IO.puts "Sending ACK" :gen_tcp.send sock, build_frame(0x01, 0, 0, <<>>) do_receive_once sock end defp send_goaway sock do IO.puts "Sending GOAWAY" no_error = 0 :gen_tcp.send sock, build_frame(0x7, 0, 0, <>) :gen_tcp.close sock end defp send_get_request sock do IO.puts "Sending GET request" end_headers = 0x4 :gen_tcp.send sock, build_frame(0x1, end_headers, 1, << <<0::1, 0::1, 0::1, 1::1, 0::4, 0::1, byte_size("scheme")::7, "scheme", 0::1, byte_size("http")::7, "http">>, <<0::1, 0::1, 0::1, 1::1, 0::4, 0::1, byte_size("authority")::7, "authority", 0::1, byte_size("nghttp2.org")::7, "nghttp2.org">>, <<0::1, 0::1, 0::1, 1::1, 0::4, 0::1, byte_size("path")::7, "path", 0::1, byte_size("/httpbin/")::7, "/httpbin/">>, <<0::1, 0::1, 0::1, 1::1, 0::4, 0::1, byte_size("method")::7, "method", 0::1, byte_size("GET")::7, "GET">> >>) do_receive_once sock end defp build_frame frame_type, flags, stream_id, payload do header = <<0::2, byte_size(payload)::14, frame_type::8, flags::8, 0::1, stream_id::31>> IO.inspect header <> end defp do_receive_ssl sock do receive do {:tcp, sock, bin} -> {:ok, bin} after 5000 -> {:error, "timeout."} end end defp do_receive_once sock do receive do {:tcp, sock, bin} -> IO.inspect to_string(bin) <<0::2, payload_size::14, frame_type::8, flags::8, 0::1, stream_id::31, payload::binary>> = bin case frame_type do 0x3 -> <> = payload {:error, "RST_STREAM error_code:#{error_code}"} 0x7 -> <> = payload {:error, "GOAWAY error_code:#{error_code}"} _ -> {:ok, bin} end {:ssl_closed, sock} -> {:error, "closed."} {:ssl_error, sock, reason} -> {:error, reason} whatever -> IO.inspect whatever after 5000 -> {:error, "timeout."} end end # defp do_receive_response sock, header \\ [], body \\ [] do # end end