1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| package keys type ctxKey string const ( KeyTraceID ctxKey = "traceID" KeyUserID ctxKey = "userID" ) func WithTraceID(ctx context.Context, id string) context.Context { return context.WithValue(ctx, KeyTraceID, id) } func TraceID(ctx context.Context) (string, bool) { v, ok := ctx.Value(KeyTraceID).(string); return v, ok }
func GetProfileHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel()
if reqID := r.Header.Get("X-Request-ID"); reqID != "" { ctx = keys.WithTraceID(ctx, reqID) } ctx = context.WithValue(ctx, keys.KeyUserID, "u123")
prof, err := svc.GetProfile(ctx, "u123") if err != nil { switch { case errors.Is(err, context.DeadlineExceeded): http.Error(w, "timeout", http.StatusGatewayTimeout) return case errors.Is(err, context.Canceled): return default: http.Error(w, "internal error", http.StatusInternalServerError) return } } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(prof)) }
type Service struct { DAO *DAO Http *http.Client } func (s *Service) GetProfile(ctx context.Context, uid string) (string, error) { if dl, ok := ctx.Deadline(); ok && time.Until(dl) < 300*time.Millisecond { return "", fmt.Errorf("no budget: %w", context.DeadlineExceeded) }
g, ctx := errgroup.WithContext(ctx) var ( base string enrich string ) g.Go(func() error { v, err := s.DAO.QueryBase(ctx, uid) if err != nil { return err } base = v; return nil }) g.Go(func() error { req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/enrich?uid="+uid, nil) resp, err := s.Http.Do(req) if err != nil { return err } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) enrich = string(b) return nil }) if err := g.Wait(); err != nil { return "", err }
return fmt.Sprintf(`{"base":%q,"enrich":%q}`, base, enrich), nil }
type DAO struct { DB *sql.DB } func (d *DAO) QueryBase(ctx context.Context, uid string) (string, error) { row := d.DB.QueryRowContext(ctx, "SELECT name FROM users WHERE id=?", uid) var name string if err := row.Scan(&name); err != nil { return "", err } return name, nil }
|