Request Handling Guide
Master request processing in Squirrel, including parsing forms, handling JSON, working with headers, and file uploads.
JSONFile UploadForm Data
Reading Request Data
Access different types of request data (query params, headers, body)
package main
import (
"fmt"
"net/http"
"strconv"
"github.com/squirrel-land/squirrel"
)
func searchHandler(w http.ResponseWriter, r *http.Request) {
// Get query parameters
query := r.URL.Query()
// Single value
searchTerm := query.Get("q")
if searchTerm == "" {
http.Error(w, "Missing search query", http.StatusBadRequest)
return
}
// Multiple values
categories := query["category"] // []string
// With default value
pageStr := query.Get("page")
page := 1
if pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil {
page = p
}
}
// Limit with validation
limitStr := query.Get("limit")
limit := 10 // default
if limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
limit = l
}
}
fmt.Fprintf(w, "Search: %s, Page: %d, Limit: %d, Categories: %v",
searchTerm, page, limit, categories)
}
func main() {
mux := squirrel.NewSqurlMux()
// Example: /search?q=golang&category=web&category=api&page=2&limit=20
mux.HandleFunc("/search", searchHandler)
http.ListenAndServe(":8080", mux)
}JSON Request Handling
Parse and validate JSON request bodies
package main
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/squirrel-land/squirrel"
)
type User struct {
ID int `json:"id"`
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18,max=120"`
}
type ErrorResponse struct {
Error string `json:"error"`
Details map[string]string `json:"details,omitempty"`
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
// Ensure content type is JSON
if !strings.Contains(r.Header.Get("Content-Type"), "application/json") {
writeErrorResponse(w, "Content-Type must be application/json", http.StatusBadRequest)
return
}
// Limit request body size (1MB)
r.Body = http.MaxBytesReader(w, r.Body, 1048576)
var user User
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // Reject unknown fields
if err := decoder.Decode(&user); err != nil {
var errorMsg string
switch {
case strings.Contains(err.Error(), "unknown field"):
errorMsg = "Unknown field in JSON"
case strings.Contains(err.Error(), "invalid character"):
errorMsg = "Invalid JSON format"
default:
errorMsg = "Error parsing JSON"
}
writeErrorResponse(w, errorMsg, http.StatusBadRequest)
return
}
// Validate required fields
if user.Name == "" {
writeErrorResponse(w, "Name is required", http.StatusBadRequest)
return
}
if user.Email == "" {
writeErrorResponse(w, "Email is required", http.StatusBadRequest)
return
}
// Basic email validation
if !strings.Contains(user.Email, "@") {
writeErrorResponse(w, "Invalid email format", http.StatusBadRequest)
return
}
// Age validation
if user.Age < 18 || user.Age > 120 {
writeErrorResponse(w, "Age must be between 18 and 120", http.StatusBadRequest)
return
}
// Simulate saving user (assign ID)
user.ID = 123
// Return created user
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func writeErrorResponse(w http.ResponseWriter, message string, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(ErrorResponse{Error: message})
}
// Batch processing example
func batchCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
var users []User
if err := json.NewDecoder(r.Body).Decode(&users); err != nil {
writeErrorResponse(w, "Invalid JSON array", http.StatusBadRequest)
return
}
if len(users) == 0 {
writeErrorResponse(w, "Empty user array", http.StatusBadRequest)
return
}
if len(users) > 100 {
writeErrorResponse(w, "Too many users (max 100)", http.StatusBadRequest)
return
}
// Process each user
var createdUsers []User
var errors []string
for i, user := range users {
if user.Name == "" || user.Email == "" {
errors = append(errors, fmt.Sprintf("User %d: missing required fields", i))
continue
}
user.ID = 1000 + i // Assign ID
createdUsers = append(createdUsers, user)
}
response := map[string]interface{}{
"created": createdUsers,
"errors": errors,
"total": len(users),
"success": len(createdUsers),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main() {
mux := squirrel.NewSqurlMux()
mux.Post("/users", createUserHandler)
mux.Post("/users/batch", batchCreateUsersHandler)
http.ListenAndServe(":8080", mux)
}Form Data Handling
Process HTML forms and URL-encoded data
package main
import (
"fmt"
"net/http"
"strconv"
"github.com/squirrel-land/squirrel"
)
func contactFormHandler(w http.ResponseWriter, r *http.Request) {
// Parse form data
if err := r.ParseForm(); err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
// Get form values
name := r.FormValue("name")
email := r.FormValue("email")
message := r.FormValue("message")
// Get multiple values (checkboxes, multiple selects)
interests := r.Form["interests"] // []string
// Get with default value
newsletter := r.FormValue("newsletter") == "on"
// Convert string to int
ageStr := r.FormValue("age")
age := 0
if ageStr != "" {
if a, err := strconv.Atoi(ageStr); err == nil {
age = a
}
}
response := fmt.Sprintf(`Form Data Received:
Name: %s
Email: %s
Age: %d
Message: %s
Newsletter: %t
Interests: %v`, name, email, age, message, newsletter, interests)
fmt.Fprint(w, response)
}
// Handle both GET (show form) and POST (process form)
func userFormHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// Show HTML form
html := `<!DOCTYPE html>
<html>
<head><title>User Form</title></head>
<body>
<form method="POST">
<label>Name: <input type="text" name="name" required></label><br>
<label>Email: <input type="email" name="email" required></label><br>
<label>Age: <input type="number" name="age" min="18"></label><br>
<label>Message: <textarea name="message"></textarea></label><br>
<label><input type="checkbox" name="newsletter"> Subscribe to newsletter</label><br>
<label>Interests:</label><br>
<label><input type="checkbox" name="interests" value="web"> Web Development</label><br>
<label><input type="checkbox" name="interests" value="mobile"> Mobile Development</label><br>
<label><input type="checkbox" name="interests" value="ai"> AI/ML</label><br>
<button type="submit">Submit</button>
</form>
</body>
</html>`
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, html)
case http.MethodPost:
contactFormHandler(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func main() {
mux := squirrel.NewSqurlMux()
mux.HandleFunc("/contact", contactFormHandler)
mux.HandleFunc("/form", userFormHandler)
http.ListenAndServe(":8080", mux)
}File Upload Handling
Handle single and multiple file uploads with validation
package main
import (
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/squirrel-land/squirrel"
)
const (
MaxFileSize = 10 << 20 // 10MB
UploadDir = "./uploads"
)
func init() {
// Create upload directory if it doesn't exist
os.MkdirAll(UploadDir, 0755)
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// Limit request size
r.Body = http.MaxBytesReader(w, r.Body, MaxFileSize)
// Parse multipart form
if err := r.ParseMultipartForm(MaxFileSize); err != nil {
http.Error(w, "File too large", http.StatusBadRequest)
return
}
// Get file from form
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error retrieving file", http.StatusBadRequest)
return
}
defer file.Close()
// Validate file
if err := validateFile(header); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Create unique filename
filename := generateUniqueFilename(header.Filename)
filepath := filepath.Join(UploadDir, filename)
// Create destination file
dst, err := os.Create(filepath)
if err != nil {
http.Error(w, "Error creating file", http.StatusInternalServerError)
return
}
defer dst.Close()
// Copy file content
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
return
}
// Return success response
response := map[string]interface{}{
"success": true,
"filename": filename,
"size": header.Size,
"message": "File uploaded successfully",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func validateFile(header *multipart.FileHeader) error {
// Check file size
if header.Size > MaxFileSize {
return fmt.Errorf("file too large (max %d bytes)", MaxFileSize)
}
if header.Size == 0 {
return fmt.Errorf("empty file")
}
// Check file extension
allowedExts := []string{".jpg", ".jpeg", ".png", ".gif", ".pdf", ".txt", ".doc", ".docx"}
ext := strings.ToLower(filepath.Ext(header.Filename))
allowed := false
for _, allowedExt := range allowedExts {
if ext == allowedExt {
allowed = true
break
}
}
if !allowed {
return fmt.Errorf("file type not allowed: %s", ext)
}
return nil
}
func generateUniqueFilename(originalName string) string {
ext := filepath.Ext(originalName)
name := strings.TrimSuffix(originalName, ext)
// Clean filename
name = strings.ReplaceAll(name, " ", "_")
name = regexp.MustCompile(`[^a-zA-Z0-9_-]`).ReplaceAllString(name, "")
// Add timestamp for uniqueness
timestamp := time.Now().Unix()
return fmt.Sprintf("%s_%d%s", name, timestamp, ext)
}
// Multiple file upload
func multipleUploadHandler(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, MaxFileSize*5) // Allow up to 5 files
if err := r.ParseMultipartForm(MaxFileSize * 5); err != nil {
http.Error(w, "Request too large", http.StatusBadRequest)
return
}
files := r.MultipartForm.File["files"]
if len(files) == 0 {
http.Error(w, "No files provided", http.StatusBadRequest)
return
}
if len(files) > 5 {
http.Error(w, "Too many files (max 5)", http.StatusBadRequest)
return
}
var uploadedFiles []map[string]interface{}
var errors []string
for i, fileHeader := range files {
file, err := fileHeader.Open()
if err != nil {
errors = append(errors, fmt.Sprintf("File %d: error opening file", i))
continue
}
if err := validateFile(fileHeader); err != nil {
errors = append(errors, fmt.Sprintf("File %d: %s", i, err.Error()))
file.Close()
continue
}
filename := generateUniqueFilename(fileHeader.Filename)
filepath := filepath.Join(UploadDir, filename)
dst, err := os.Create(filepath)
if err != nil {
errors = append(errors, fmt.Sprintf("File %d: error creating file", i))
file.Close()
continue
}
if _, err := io.Copy(dst, file); err != nil {
errors = append(errors, fmt.Sprintf("File %d: error saving file", i))
dst.Close()
file.Close()
continue
}
uploadedFiles = append(uploadedFiles, map[string]interface{}{
"original": fileHeader.Filename,
"filename": filename,
"size": fileHeader.Size,
})
dst.Close()
file.Close()
}
response := map[string]interface{}{
"success": len(uploadedFiles) > 0,
"uploaded": uploadedFiles,
"errors": errors,
"total_files": len(files),
"success_count": len(uploadedFiles),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main() {
mux := squirrel.NewSqurlMux()
mux.Post("/upload", uploadHandler)
mux.Post("/upload/multiple", multipleUploadHandler)
// Serve uploaded files
mux.HandleFunc("/files/", http.StripPrefix("/files/",
http.FileServer(http.Dir(UploadDir))).ServeHTTP)
http.ListenAndServe(":8080", mux)
}Security Note: Always validate file types, sizes, and content. Never trust user input. Consider using virus scanning for uploaded files in production environments.