main.go 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "net/url"
  9. "os"
  10. "strings"
  11. "time"
  12. )
  13. // Greeting 是服务端返回的 JSON 结构。
  14. type Greeting struct {
  15. Message string `json:"message"`
  16. }
  17. // greetingHandler 是 HTTP 服务端的处理函数。
  18. func greetingHandler(w http.ResponseWriter, r *http.Request) {
  19. // 本课只接受 GET。状态码不是 Go 函数的 error 返回值。
  20. if r.Method != http.MethodGet {
  21. w.Header().Set("Allow", http.MethodGet)
  22. http.Error(w, "只支持 GET", http.StatusMethodNotAllowed)
  23. return
  24. }
  25. name := strings.TrimSpace(r.URL.Query().Get("name"))
  26. if name == "" {
  27. http.Error(w, "name 不能为空", http.StatusBadRequest)
  28. return
  29. }
  30. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  31. // 首次写入响应体时会默认发送 200 状态码。
  32. if err := json.NewEncoder(w).Encode(Greeting{Message: "你好," + name + "!"}); err != nil {
  33. // 响应可能已开始,不能保证此时还能改状态码。
  34. fmt.Fprintln(os.Stderr, "写入响应失败:", err)
  35. }
  36. }
  37. // fetchGreeting 是客户端:发送请求、检查状态、读取并解析 JSON。
  38. func fetchGreeting(client *http.Client, baseURL, name string) (Greeting, error) {
  39. params := url.Values{}
  40. params.Set("name", name) // 对中文、空格和特殊字符做 URL 编码。
  41. response, err := client.Get(baseURL + "/hello?" + params.Encode())
  42. if err != nil {
  43. return Greeting{}, fmt.Errorf("发送请求:%w", err)
  44. }
  45. defer response.Body.Close() // 只在请求成功后关闭响应体。
  46. if response.StatusCode != http.StatusOK {
  47. return Greeting{}, fmt.Errorf("服务端返回状态码 %d", response.StatusCode)
  48. }
  49. // 为示例设置大小上限,避免无限读取响应体。
  50. const maxBody = 64 * 1024
  51. data, err := io.ReadAll(io.LimitReader(response.Body, maxBody+1))
  52. if err != nil {
  53. return Greeting{}, fmt.Errorf("读取响应:%w", err)
  54. }
  55. if len(data) > maxBody {
  56. return Greeting{}, fmt.Errorf("响应内容超过 %d 字节", maxBody)
  57. }
  58. var result Greeting
  59. if err := json.Unmarshal(data, &result); err != nil {
  60. return Greeting{}, fmt.Errorf("解析 JSON:%w", err)
  61. }
  62. return result, nil
  63. }
  64. func run() error {
  65. // 新建路由器:只将 /hello 交给我们的处理函数。
  66. mux := http.NewServeMux()
  67. mux.HandleFunc("/hello", greetingHandler)
  68. // 本课使用本机临时测试服务器,自动选择可用端口,不访问外网。
  69. server := httptest.NewServer(mux)
  70. defer server.Close()
  71. fmt.Println("本地临时服务:", server.URL)
  72. // 设置超时,避免请求无限等待。
  73. client := &http.Client{Timeout: 3 * time.Second}
  74. result, err := fetchGreeting(client, server.URL, "小朱")
  75. if err != nil {
  76. return err
  77. }
  78. fmt.Println("客户端收到:", result.Message)
  79. fmt.Println("演示结束,临时服务即将关闭")
  80. return nil
  81. }
  82. func main() {
  83. if err := run(); err != nil {
  84. fmt.Fprintln(os.Stderr, "请求失败:", err)
  85. os.Exit(1)
  86. }
  87. }