【Goで学ぶコンピューターサイエンス】HTTPの仕組み

## **HTTPの特徴**

### ステートレス
HTTPは「ステートレス（状態を持たない）」なプロトコルです。一度の通信が終了すると、サーバーはその通信の状態を保持しません。  
例：AさんがページAを開いた後にページBを開いても、サーバーはAさんが以前にページAを見たことを覚えていません。

この問題を解決するために**Cookie**や**セッション**が利用されます。

### リクエストとレスポンスの組み合わせ
HTTP通信は、クライアントからのリクエストとサーバーからのレスポンスによって成り立ちます。

## **HTTPのリクエスト**
### **リクエストの構造**
```
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
<空行が入る>
<ボディ（必要に応じて）>
```

- **メソッド**：リクエストの種類を指定（例：GET, POST, PUT, DELETE）
- **リクエストターゲット**：リソース（例：`/index.html`）の場所を指定
- **HTTPバージョン**：HTTPのバージョン（例：HTTP/1.1, HTTP/2）
- **ヘッダー情報**：リクエストに関する追加情報（例：`User-Agent`でクライアントの種類を指定）
- **ボディ**：データを送信する場合に使用（例：フォームのデータなど）

## **HTTPのレスポンス**
```
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 138

<html>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>
```

- **ステータスコード**：リクエストの結果を表す番号（例：200, 404, 500）
  - `200 OK`：成功
  - `404 Not Found`：リソースが見つからない
  - `500 Internal Server Error`：サーバーエラー
- **ヘッダー情報**：レスポンスに関する追加情報（例：`Content-Type`でデータ形式を指定）
- **ボディ**：クライアントに返すデータ（例：HTML, JSON）

## **HTTPメソッド**

HTTPメソッドは、クライアントがサーバーに対してどのような動作を要求するかを示します。

| メソッド | 説明                           | 主な用途        |
| -------- | ------------------------------ | --------------- |
| GET      | リソースを取得する             | Webページの表示 |
| POST     | データを送信して処理を要求する | フォーム送信    |
| PUT      | リソースを更新する             | データの更新    |
| DELETE   | リソースを削除する             | データの削除    |
| HEAD     | リソースのヘッダーだけを取得   | 状態確認        |

## **HTTPとHTTPSの違い**

### **HTTP**
- データは暗号化されず、平文で送信される
- セキュリティに問題がある（盗聴や改ざんのリスク）

### **HTTPS**
- データをSSL/TLSで暗号化して送信
- セキュリティが向上し、安全な通信を実現

### **HTTPSの仕組み**
1. サーバーがSSL証明書を使ってクライアントに自身を証明
2. クライアントとサーバーが共通鍵を生成し、データを暗号化

## **HTTPの進化**

### **HTTP/1.1**
- 一度の接続で複数リクエストを送れる「keep alive」をサポート

### **HTTP/2**
- データをバイナリ形式で送信し、高速化
- ヘッダー圧縮により通信量を削減
- 1つの接続で複数のデータを同時に送信

### **HTTP/3**
- UDPベースの通信プロトコル「QUIC」を採用
- 接続速度の高速化と遅延の改善

## **Go言語でHTTPを扱う例**

### HTTPサーバーの基本的な実装
```go
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	// クライアントからのリクエスト情報をログに出力
	fmt.Printf("Method: %s, Path: %s\n", r.Method, r.URL.Path)
	
	// レスポンスを送信
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	fmt.Fprintln(w, "Hello, HTTP Server!")
}

func main() {
	http.HandleFunc("/", handler)
	fmt.Println("Starting server on :8080")
	http.ListenAndServe(":8080", nil)
}

```

サーバー側
```
Starting server on :8080
Method: GET, Path: /
```

クライアント側
![スクリーンショット 2024-12-16 23.40.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603224/94f7a10a-e583-3079-e2b2-a5796bfc4294.png)


### HTTPクライアントの基本的な実装
```go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	// GETリクエストを送信
	resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer resp.Body.Close()

	// ステータスコードを出力
	fmt.Println("Status Code:", resp.StatusCode)

	// レスポンスボディを読み取る
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error reading body:", err)
		return
	}

	fmt.Println("Response Body:", string(body))
}
```

```
Status Code: 200
Response Body: {
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
```

### JSONデータの送受信
```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type RequestData struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
		return
	}

	var data RequestData
	if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	response := fmt.Sprintf("Received: Name=%s, Age=%d", data.Name, data.Age)
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"message": response})
}

func main() {
	http.HandleFunc("/submit", jsonHandler)
	fmt.Println("Server started at :8080")
	http.ListenAndServe(":8080", nil)
}
```

postman等でPOSTリクエストを送信してリクエストを確認
```
{
    "message": "Received: Name=taro, Age=20"
}
```