英文:
"localhost didn't send any data" when using Golang + Gin + Docker
问题
我已经创建了一个简单的API,按照YouTube教程进行操作,在本地完美运行。一旦我将应用程序容器化并运行容器,我无法访问http://localhost:8080上的API。我猜测这与我在dockerfile中使用的端口设置有关,但我不确定。
main.go文件:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"errors"
)
type phone struct{
ID string `json:"id"`
Model string `json:"model"`
Year string `json:"year"`
Quantity int `json:"quantity"`
}
var phones = []phone{
{ID: "1", Model: "iPhone 11", Year: "2019", Quantity: 4},
{ID: "2", Model: "iPhone 6", Year: "2014", Quantity: 9},
{ID: "3", Model: "iPhone X", Year: "2017", Quantity: 2},
}
func phoneById(c *gin.Context) {
id := c.Param("id")
phone, err := getPhoneById(id)
if err != nil {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "Phone not found."})
return
}
c.IndentedJSON(http.StatusOK, phone)
}
func checkoutPhone(c *gin.Context) {
id, ok := c.GetQuery("id")
if !ok {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Missing id query paramater"})
return
}
phone, err := getPhoneById(id)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not found"})
return
}
if phone.Quantity <= 0 {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not available."})
return
}
phone.Quantity -= 1
c.IndentedJSON(http.StatusOK, phone)
}
func returnPhone(c *gin.Context) {
id, ok := c.GetQuery("id")
if !ok {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Missing id query paramater"})
return
}
phone, err := getPhoneById(id)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not found"})
return
}
if phone.Quantity <= 0 {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not available."})
return
}
phone.Quantity += 1
c.IndentedJSON(http.StatusOK, phone)
}
func getPhoneById(id string) (*phone, error) {
for i, p := range phones {
if p.ID == id {
return &phones[i], nil
}
}
return nil, errors.New("Phone not found.")
}
func getPhones(c *gin.Context) {
c.IndentedJSON(http.StatusOK, phones)
}
func createPhone(c *gin.Context) {
var newPhone phone
if err := c.BindJSON(&newPhone); err != nil {
return
}
phones = append(phones, newPhone)
c.IndentedJSON(http.StatusCreated, newPhone)
}
func main(){
router := gin.Default()
router.GET("/phones", getPhones)
router.GET("/phones/:id", phoneById)
router.POST("/phones", createPhone)
router.PATCH("/checkout", checkoutPhone)
router.PATCH("/return", returnPhone)
router.Run("localhost:8080")
}
我的dockerfile:
#The standard golang image contains all of the resources to build
#But is very large. So build on it, then copy the output to the
#final runtime container
FROM golang:latest AS buildContainer
WORKDIR /go/src/app
COPY . .
#flags: -s -w to remove symbol table and debug info
#CGO_ENALBED=0 is required for the code to run properly when copied alpine
RUN CGO_ENABLED=0 GOOS=linux go build -v -mod mod -ldflags "-s -w" -o restapi .
#Now build the runtime container, just a stripped down linux and copy the
#binary to it.
FROM alpine:latest
WORKDIR /app
COPY --from=buildContainer /go/src/app/restapi .
ENV GIN_MODE release
ENV HOST 0.0.0.0
ENV PORT 8080
EXPOSE 8080
CMD ["./restapi"]
我尝试了在Google上找到的不同dockerfile,并尝试从头开始创建自己的dockerfile。
英文:
I've created a simple API following a youtube tutorial that works perfectly locally. Once I containerise the app and run the container, I can't access the API at http://localhost:8080. I'm guessing it has something to do with the port settings I'm using in the dockerfile, but I'm not sure.
main.go file:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"errors"
)
type phone struct{
ID string `json:"id"`
Model string `json:"model"`
Year string `json:"year"`
Quantity int `json:"quantity"`
}
var phones = []phone{
{ID: "1", Model: "iPhone 11", Year: "2019", Quantity: 4},
{ID: "2", Model: "iPhone 6", Year: "2014", Quantity: 9},
{ID: "3", Model: "iPhone X", Year: "2017", Quantity: 2},
}
func phoneById(c *gin.Context) {
id := c.Param("id")
phone, err := getPhoneById(id)
if err != nil {
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "Phone not found."})
return
}
c.IndentedJSON(http.StatusOK, phone)
}
func checkoutPhone(c *gin.Context) {
id, ok := c.GetQuery("id")
if !ok {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Missing id query paramater"})
return
}
phone, err := getPhoneById(id)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not found"})
return
}
if phone.Quantity <= 0 {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not available."})
return
}
phone.Quantity -= 1
c.IndentedJSON(http.StatusOK, phone)
}
func returnPhone(c *gin.Context) {
id, ok := c.GetQuery("id")
if !ok {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Missing id query paramater"})
return
}
phone, err := getPhoneById(id)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not found"})
return
}
if phone.Quantity <= 0 {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Message": "Phone not available."})
return
}
phone.Quantity += 1
c.IndentedJSON(http.StatusOK, phone)
}
func getPhoneById(id string) (*phone, error) {
for i, p := range phones {
if p.ID == id {
return &phones[i], nil
}
}
return nil, errors.New("Phone not found.")
}
func getPhones(c *gin.Context) {
c.IndentedJSON(http.StatusOK, phones)
}
func createPhone(c *gin.Context) {
var newPhone phone
if err := c.BindJSON(&newPhone); err != nil {
return
}
phones = append(phones, newPhone)
c.IndentedJSON(http.StatusCreated, newPhone)
}
func main(){
router := gin.Default()
router.GET("/phones", getPhones)
router.GET("/phones/:id", phoneById)
router.POST("/phones", createPhone)
router.PATCH("/checkout", checkoutPhone)
router.PATCH("/return", returnPhone)
router.Run("localhost:8080")
}
and my dockerfile:
#The standard golang image contains all of the resources to build
#But is very large. So build on it, then copy the output to the
#final runtime container
FROM golang:latest AS buildContainer
WORKDIR /go/src/app
COPY . .
#flags: -s -w to remove symbol table and debug info
#CGO_ENALBED=0 is required for the code to run properly when copied alpine
RUN CGO_ENABLED=0 GOOS=linux go build -v -mod mod -ldflags "-s -w" -o restapi .
#Now build the runtime container, just a stripped down linux and copy the
#binary to it.
FROM alpine:latest
WORKDIR /app
COPY --from=buildContainer /go/src/app/restapi .
ENV GIN_MODE release
ENV HOST 0.0.0.0
ENV PORT 8080
EXPOSE 8080
CMD ["./restapi"]
I've tried different dockerfiles found on Google, and tried creating my own from scratch.
答案1
得分: 4
你需要在容器内部绑定到公共网络接口。因为每个容器都是自己的主机,当你在内部绑定到回环接口时,它将无法被外部访问。
router.Run("0.0.0.0:8080")
此外,在运行容器时,请确保发布此端口。
docker run --publish 8080:8080 myapp
实际上,你在环境变量中正确地指示了意图,但是它们在你的代码中没有被使用。
ENV HOST 0.0.0.0
ENV PORT 8080
你可以使用 os.Getenv 或 os.LookupEnv 从你的代码中获取这些变量并使用它们。
英文:
You need to bind to the public network interface inside the container. Because each container is its own host, and when you bind to the loopback interface inside, it will not be accessible to the outside world.
router.Run("0.0.0.0:8080")
Additionally, make sure you publish this port when running the container.
docker run --publish 8080:8080 myapp
You actually indicate the right intend with your environment variables, but they are not used in your code.
ENV HOST 0.0.0.0
ENV PORT 8080
You can use os.Getenv or os.LookupEnv to get those variables from your code and use them.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论