package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"strings"
	"time"
)

func main() {
	// Bagian 1: client.Timeout memutus permintaan yang terlalu lama.
	tsLambat := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(100 * time.Millisecond) // server sengaja lambat mengirim respons
		fmt.Fprintln(w, "akhirnya selesai")
	}))
	defer tsLambat.Close()

	klienCepat := &http.Client{Timeout: 30 * time.Millisecond}

	_, err := klienCepat.Get(tsLambat.URL + "/lambat")
	fmt.Println("permintaan lambat gagal karena timeout:", err != nil)
	fmt.Println("error bertipe deadline:", errors.Is(err, context.DeadlineExceeded))
	if err != nil && strings.Contains(err.Error(), "context deadline") {
		pesan := err.Error()
		i := strings.Index(pesan, "context deadline")
		fmt.Println("pesan inti error:", pesan[i:])
	}

	// Bagian 2: ReadHeaderTimeout memutus klien yang tersambung tapi diam.
	tsKetat := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "ok")
	}))
	tsKetat.Config.ReadHeaderTimeout = 60 * time.Millisecond
	tsKetat.Start()
	defer tsKetat.Close()

	koneksi, err := net.Dial("tcp", strings.TrimPrefix(tsKetat.URL, "http://"))
	if err != nil {
		fmt.Println("gagal terhubung ke server:", err)
		return
	}
	// klien nakal: sudah tersambung, tetapi tidak pernah mengirim permintaan.
	time.Sleep(200 * time.Millisecond)

	buf := make([]byte, 1)
	_, err = koneksi.Read(buf)
	fmt.Println("server memutus klien yang diam:", errors.Is(err, io.EOF))
	koneksi.Close()

	// Bagian 3: keep-alive memakai ulang koneksi TCP yang sama.
	var jumlahKoneksi int64
	dialer := &net.Dialer{Timeout: 2 * time.Second}
	transport := &http.Transport{
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			jumlahKoneksi++
			return dialer.DialContext(ctx, network, addr)
		},
	}
	klien := &http.Client{Transport: transport, Timeout: 2 * time.Second}

	tsStabil := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "pong")
	}))
	defer tsStabil.Close()

	ping := func() {
		resp, err := klien.Get(tsStabil.URL)
		if err != nil {
			return
		}
		io.Copy(io.Discard, resp.Body)
		resp.Body.Close()
	}

	ping()
	ping()
	fmt.Println("dua permintaan keep-alive, koneksi TCP dibuat:", jumlahKoneksi)

	req, err := http.NewRequest(http.MethodGet, tsStabil.URL, nil)
	if err != nil {
		return
	}
	req.Close = true // kirim header Connection: close
	resp, err := klien.Do(req)
	if err == nil {
		io.Copy(io.Discard, resp.Body)
		resp.Body.Close()
	}
	fmt.Println("setelah Connection: close, koneksi TCP dibuat:", jumlahKoneksi)

	ping() // koneksi lama sudah ditutup, klien harus mendial ulang
	fmt.Println("permintaan setelah koneksi ditutup, koneksi TCP dibuat:", jumlahKoneksi)
}
