You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GO Docker容器REST服务返回空响应问题排查求助

无法从Go Docker容器获取服务器响应,curl返回Empty reply from server

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 17:57:32