Whenever I try sending an HTTP request from my angular application to my Go server, I get the following response:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I added the headers detailed in the error response but the error persists.
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, POST"},
AllowedOrigins: []string{"http://localhost:4200"},
AllowedHeaders: []string{"Content-Type", "Accept"},
})
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) {
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:4200")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Accept, origin, Cache-Control, X-Requested-With")
if r.Method == "OPTIONS" {
return
}
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.MethodPost)
r.HandleFunc("/weather/welcome", handler.Test()).Methods(http.MethodGet)
}
The first issue I noted is:
AllowedMethods: []string{"GET, POST"},
This should be:
AllowedMethods: []string{"GET", "POST"},
(or the slightly less error prone []string{http.MethodGet, http.MethodPost}
)
As per the docs this is the default (so you could just have omitted this):
AllowedMethods
[]string
: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (GET and POST).
My next question would be "What is the Origin"; CORS is "Cross-Origin Resource Sharing" and aims to "prevent a client-side Web application running from one origin from obtaining data retrieved from another origin" so it's important to know the origin of the page making the request. You are allowing http://localhost:4200
(AllowedOrigins: []string{"http://localhost:4200"}
) so I assume you have two servers running on localhost (but would not be surprised if this is not the case). If you wish to allow all origins then use "*"
; for testing I'll use test-cors.org - just go to that site, enter the URL, e.g. "http://127.0.0.1:8080/weather/welcome", into the "Remote URL" and click "Send Request" to test.
Your code is a bit confusing (e.g. (s *Server) ServeHTTP
is not used - please aim to provide a minimal, reproducible, example when asking here) so I've simplified things a bit and hopefully the following points you in the right direction.
package main
import (
"context"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/rs/cors"
)
func main() {
s, err := NewServer()
if err != nil {
panic(err)
}
s.Run(context.Background())
}
type Server struct {
router *mux.Router
}
func NewServer() (*Server, error) {
router := mux.NewRouter()
Register(router)
server := Server{
router: router,
}
return &server, nil
}
func (s *Server) Run(ctx context.Context) {
cors := cors.New(cors.Options{
AllowedMethods: []string{http.MethodGet, http.MethodPost},
AllowedOrigins: []string{"https://www.test-cors.org"},
// AllowedOrigins: []string{"*"},
AllowedHeaders: []string{"Content-Type", "Accept"},
})
server := http.Server{
Addr: fmt.Sprintf(":%d", 8080),
Handler: cors.Handler(s.router),
}
fmt.Println(server.ListenAndServe())
}
type handler struct{}
func Register(r *mux.Router) {
handler := handler{}
r.HandleFunc("/", handler.Index).Methods(http.MethodGet)
r.HandleFunc("/weather/welcome", handler.Test).Methods(http.MethodGet)
}
// Index - Provide a simple page with a link to the other page to simplify testing
func (h *handler) Index(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(`<a href="weather/welcome" >Test Link</a>`))
}
// The real page
func (h *handler) Test(w http.ResponseWriter, _ *http.Request) {
fmt.Println("Test Called")
w.Write([]byte("all OK"))
}