I'm trying to create a web app using vanilla Golang (not Gin), and SvelteJS for the frontend. The web app is supposed to login to an external website (my stock broker's website), and grab the session cookie and JWT from that login request to use in subsequent requests to their API endpoint. The problem I'm facing with my current code is, the server console window shows a [2023-04-07T06:23:17.916Z] "POST /api/broker-login" Error (404): "Not found" error, yet the Chrome > Network for the same call says 405 Method Not Allowed?
What you'll find (I think) is that my file paths and handlers ARE setup correctly and pointing to the right places, AND I'm sending a POST request to my /api/broker-login endpoint. Keep in mind, I'm a newbie when it comes to JS frontend stuff, especially Svelte as this is my first crack at it. I'll try and do my best to showcase my project as concisely as possible below:
main.go:
package main
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/joho/godotenv"
)
func generateDeviceID() string {
// return uuid.New().String()
return "xxxxxxxxxxxxxxxxxxxxx" // static for testing
}
type sessionData struct {
Jwt string
Cookies []*http.Cookie
}
// Global variable to store the session data
var currentSessionData sessionData
// LoginRequest represents the payload for the login request
type LoginPayload struct {
Username string `json:"username"`
Password string `json:"password"`
DeviceID string `json:"deviceId"`
}
type LoginResponse struct {
Data struct {
Jwt string `json:"jwt"`
} `json:"Data"`
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
http.HandleFunc("/", homeHandler)
http.HandleFunc("/api/broker-login", brokerLoginHandler)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("./frontend/public/index.html")
if err != nil {
http.Error(w, "Error loading template", http.StatusInternalServerError)
return
}
t.Execute(w, nil)
}
func brokerLoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
loginURL := "https://api.xxx.co/v1/portaldata/api/login?dev=false&target=null"
// Use the credentials from the .env file instead of getting them from the request
email := os.Getenv("BROKER_EMAIL")
password := os.Getenv("BROKER_PASSWORD")
fmt.Println(email, password)
// Generate a deviceID and store it in a cookie
deviceID := generateDeviceID()
deviceIDCookie := &http.Cookie{
Name: "device_id",
Value: deviceID,
Expires: time.Now().Add(365 * 24 * time.Hour), // Set the cookie to expire in 1 year
}
http.SetCookie(w, deviceIDCookie)
// Prepare the request payload for the broker's API
payload := LoginPayload{
Username: email,
Password: password,
DeviceID: deviceID,
}
fmt.Println(payload)
// Convert the payload to JSON
payloadBytes, err := json.Marshal(payload)
if err != nil {
http.Error(w, fmt.Sprintf("failed to marshal payload: %v", err), http.StatusInternalServerError)
return
}
fmt.Println(payload)
// Send the login request to the broker's API
resp, err := http.Post(loginURL, "application/json", strings.NewReader(string(payloadBytes)))
fmt.Println(resp)
if err != nil {
log.Printf("Error in brokerLoginHandler: %v\n", err)
http.Error(w, "Failed to log in to broker", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
bodyBytes, _ := ioutil.ReadAll(resp.Body)
var loginResponse LoginResponse
json.Unmarshal(bodyBytes, &loginResponse)
jwt := loginResponse.Data.Jwt
cookies := resp.Cookies()
newSessionData := sessionData{
Jwt: jwt,
Cookies: cookies,
}
// Update the global session data
currentSessionData = newSessionData
fmt.Printf("Login Successful!\n")
}
main.js:
// Add event listener to the "Login to Broker" button
export async function loginToBroker() {
try {
const response = await fetch("/api/broker-login", { method: "POST" });
console.log(response) // trying to test...
if (response.ok) {
// Change the session status text to green
const statusText = document.querySelector("#broker-status");
statusText.textContent = "Connected to broker";
statusText.classList.remove("disconnected");
statusText.classList.add("connected");
} else {
console.error("Failed to log in to broker:", await response.text());
}
} catch (error) {
console.error("Error logging in to broker:", error);
}
}
App.svelte:
<script>
import OrderForm from "./components/OrderForm.svelte";
</script>
<main>
<h1>Stock App</h1>
<SessionStatus />
</main>
I saw in another answer that I might have been missing the export on the JS async function, so I added it, same issue with and without the export.
Does someone know what's going on here? Could it be a CORS issue? And if so, how to fix it? I've made this exact same program in Python using the requests library without issue, so I'm not sure what's going on here.
I should clarify, I run the backend server on port 8081 and the front end on the same port, and navigate to the localhost:8081 endpoint to see my login to broker button. Clicking it gives this grief. I've tried adding in some fmt.Println's to the brokerLoginHandler method as you see, but those aren't even triggered, so it's like the brokerLoginHandler isn't even being hit? Hopefully that helps someone.


fmt.Println(r.Method)as the first line inbrokerLoginHandler? You're getting a 405 back, which makes me think you're either a) not actually getting a post request, or b) possibly have an old version of your server running that is checking for a different method. I don't see where the server would be logging requests so that's a little confusing as well when you say 'the server console window shows a [2023-04-07T06:23:17.916Z] "POST /api/broker-login" Error (404): "Not found" error'exportclause was screwing things up. Why this didn't happen yesterday was what was bugging me, maybe it had to do with the browser or server cache? Not sure, but I'll post my answer here shortly. Thanks for the insight!