GO Docker容器REST服务返回空响应问题排查求助
使用curl发送GET请求localhost:8080/weather/welcome时,收到错误:curl: (52) Empty reply from server。所有服务看似运行无错误,怀疑端口暴露存在冲突,寻求问题排查与解决帮助。
.env
DATABASE_HOST=postgres_container DATABASE_PORT=5432 DATABASE_USER=postgres DATABASE_PASSWORD=postgres DATABASE_NAME=weather SERVER_PORT=8080
docker-compose.yml
services: go_container: container_name: go_container environment: - DATABASE_USER=${DATABASE_USER} - DATABASE_PASSWORD=${DATABASE_PASSWORD} - DATABASE_NAME=${DATABASE_NAME} - DATABASE_HOST=${DATABASE_HOST} - DATABASE_PORT=${DATABASE_PORT} - API_KEY=${API_KEY} build: . ports: - 8080:8080 volumes: - .:/src depends_on: postgres_container: condition: service_healthy restart: on-failure tty: true networks: - weather postgres_container: container_name: postgres_container user: postgres image: postgres:latest restart: always environment: - POSTGRES_USER=${DATABASE_USER} - POSTGRES_DB=${DATABASE_NAME} - POSTGRES_PASSWORD=${DATABASE_PASSWORD} ports: - 5432:5432 volumes: - db_data:/var/lib/postgresql/data - ./sql/create.sql:/docker-entrypoint-initdb.d/create.sql healthcheck: test: ["CMD-SHELL", "pg_isready", "-U", "${DATABASE_USER}"] networks: - weather volumes: db_data: {} networks: weather: driver: bridge
Dockerfile
FROM golang:1.21.5 EXPOSE 8080 WORKDIR /src COPY .env . COPY . . RUN go mod download && go mod verify RUN go build -o main cmd/app/main.go ENTRYPOINT [ "./main" ]
server.go
package rest import ( "context" "fmt" "net/http" "os" "os/signal" "sync" "syscall" "github.com/gorilla/mux" "github.com/randyVerduguez/randy-verduguez_06122023-BE-challenge/configs" "github.com/randyVerduguez/randy-verduguez_06122023-BE-challenge/http/rest/handlers" "github.com/randyVerduguez/randy-verduguez_06122023-BE-challenge/pkg/db" "github.com/rs/cors" "github.com/sirupsen/logrus" ) type Server struct { logger *logrus.Logger router *mux.Router config configs.Config } func NewServer() (*Server, error) { config, err := configs.NewParsedConfig() if err != nil { return nil, err } database, err := db.Connect(db.ConfigDB{ Host: config.Database.Host, Port: config.Database.Port, User: config.Database.User, Password: config.Database.Password, Name: config.Database.Name, }) if err != nil { return nil, err } log, err := NewLogger() if err != nil { return nil, err } router := mux.NewRouter() handlers.Register(router, log, database) server := Server{ logger: log, config: config, router: router, } return &server, nil } func (s *Server) Run(ctx context.Context) error { cors := cors.New(cors.Options{ AllowedMethods: []string{"GET"}, AllowedOrigins: []string{"*"}, }) server := http.Server{ Addr: fmt.Sprintf(":%d", s.config.ServerPort), Handler: cors.Handler(s.router), } stopServer := make(chan os.Signal, 1) signal.Notify(stopServer, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(stopServer) serverErrors := make(chan error, 1) var wg sync.WaitGroup wg.Add(1) go func(wg *sync.WaitGroup) { defer wg.Done() s.logger.Printf("REST API listening on %d", s.config.ServerPort) serverErrors <- server.ListenAndServe() }(&wg) select { case err := <-serverErrors: return fmt.Errorf("error: starting REST API server %w", err) case <-stopServer: s.logger.Warn("server recieved STOP signal") err := server.Shutdown(ctx) if err != nil { return fmt.Errorf("graceful shutdown did not complete: %w", err) } wg.Wait() s.logger.Info("server was shutdown gracefully") } return nil } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) }
routes.go
package handlers import ( "net/http" "github.com/gorilla/mux" "github.com/jmoiron/sqlx" "github.com/sirupsen/logrus" ) func Register(r *mux.Router, lg *logrus.Logger, db *sqlx.DB) { handler := newHandler(lg, db) r.Use(handler.MiddlewareLogger()) r.HandleFunc("/weather/current", handler.GetCurrentWeather()).Methods(http.MethodGet) r.HandleFunc("/weather/welcome", handler.Test()).Methods(http.MethodGet) }
get.go
package handlers import ( "encoding/json" "errors" "net/http" "reflect" "strings" "unicode" "github.com/randyVerduguez/randy-verduguez_06122023-BE-challenge/http/rest/types" "github.com/randyVerduguez/randy-verduguez_06122023-BE-challenge/pkg/erru" ) func (s service) GetCurrentWeather() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var request types.Request err := json.NewDecoder(r.Body).Decode(&request) if err != nil { s.respond(w, erru.ErrArgument{ Wrapped: errors.New("Invalid request body"), }, http.StatusBadRequest) return } if request == (types.Request{}) { s.respond(w, erru.ErrArgument{ Wrapped: errors.New("Invalid request body"), }, http.StatusBadRequest) return } params := reflect.ValueOf(request) for i := 0; i < params.NumField(); i++ { param := params.Field(i).String() if len(param) == 0 || !isAlpha(param) { s.respond(w, erru.ErrArgument{ Wrapped: errors.New("Invalid request body"), }, http.StatusBadRequest) return } } request.Name = strings.TrimSpace(strings.ToLower(request.Name)) request.Region = strings.TrimSpace(strings.ToLower(request.Region)) request.Country = strings.TrimSpace(strings.ToLower(request.Country)) getResponse, err := s.toDoService.Get(r.Context(), request) if err != nil { s.respond(w, err, http.StatusBadRequest) return } s.respond(w, types.Response{ Name: getResponse.Name, Region: getResponse.Region, Country: getResponse.Country, TempC: getResponse.TempC, TempF: getResponse.TempF, FeelsLikeC: getResponse.FeelsLikeC, FeelsLikeF: getResponse.FeelsLikeF, WindMph: getResponse.WindMph, WindKph: getResponse.WindKph, Humidity: getResponse.Humidity, CreatedOn: getResponse.CreatedOn, }, http.StatusOK) } } func (s service) Test() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { s.respond(w, "Welcome :)", http.StatusOK) } } func isAlpha(s string) bool { for _, c := range s { if unicode.IsSpace(c) { continue } if !unicode.IsLetter(c) { return false } } return true }
问题排查与解决方案
检查端口占用:
在宿主机执行lsof -i :8080或netstat -tulpn | grep 8080,确认8080端口是否被其他进程占用。如果是,停止占用进程或修改docker-compose.yml中go_container的端口映射(如改为8081:8080),然后重启服务。验证Go服务启动状态:
查看Go容器日志:docker logs go_container,确认是否有"REST API listening on 8080"的日志输出,以及是否存在数据库连接失败、配置解析错误等异常。根据日志修复对应问题。容器内服务连通性测试:
进入Go容器内部:docker exec -it go_container bash,在容器内执行curl localhost:8080/weather/welcome。如果容器内可正常响应,说明问题出在端口映射或宿主机网络;如果容器内也报错,说明服务启动或路由配置存在问题。检查响应逻辑:
确认respond函数是否正确处理响应写入,比如是否设置了正确的Content-Type头,是否将数据完整写入响应体。空回复往往是因为响应未被正确输出。验证数据库依赖:
确认postgres_container的健康检查是否通过,go_container是否能正常连接数据库。如果数据库连接失败,服务可能无法完成启动流程,导致无法处理请求。
内容的提问来源于stack exchange,提问作者Papilo

