github.com/lauslim12/expert-systems@v0.0.0-20221115131159-018513aad29c/internal/application/nethttp.go (about) 1 package application 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "os" 8 "time" 9 10 "github.com/go-chi/chi/v5" 11 "github.com/go-chi/chi/v5/middleware" 12 "github.com/go-chi/httprate" 13 "github.com/lauslim12/expert-systems/pkg/inference" 14 ) 15 16 // Application constants for application modes. 17 const ( 18 applicationModeDevelopment = "development" 19 applicationModeProduction = "production" 20 ) 21 22 // SuccessResponse is used to handle successful responses. 23 type SuccessResponse struct { 24 Status string `json:"status"` 25 Code int `json:"code"` 26 Message string `json:"message"` 27 Data interface{} `json:"data,omitempty"` 28 } 29 30 // FailureResponse is used to handle failed requests. 31 type FailureResponse struct { 32 Status string `json:"status"` 33 Code int `json:"code"` 34 Message string `json:"message"` 35 } 36 37 // NewSuccessResponse will create an instance of 'SuccessResponse' with default values. 38 func NewSuccessResponse(code int, message string, data interface{}) *SuccessResponse { 39 return &SuccessResponse{ 40 Status: "success", 41 Code: code, 42 Message: message, 43 Data: data, 44 } 45 } 46 47 // NewFailureResponse will create an instance of 'FailureResponse' with default values. 48 func NewFailureResponse(code int, message string) *FailureResponse { 49 return &FailureResponse{ 50 Status: "fail", 51 Code: code, 52 Message: message, 53 } 54 } 55 56 // Utility function to send back success response to users. 57 func sendSuccessResponse(w http.ResponseWriter, successResponse *SuccessResponse) { 58 w.Header().Set("Content-Type", "application/json") 59 w.WriteHeader(successResponse.Code) 60 json.NewEncoder(w).Encode(successResponse) 61 } 62 63 // Utility function to send back error response to users. 64 func sendFailureResponse(w http.ResponseWriter, failureResponse *FailureResponse) { 65 w.Header().Set("Content-Type", "application/json") 66 w.WriteHeader(failureResponse.Code) 67 json.NewEncoder(w).Encode(failureResponse) 68 } 69 70 // Initialize application. 71 // The 'pathToWebDirectory' is usually filled with './web/build' (the built React application location). 72 func Configure(pathToWebDirectory, applicationMode string) http.Handler { 73 // Create a Chi instance. 74 r := chi.NewRouter() 75 76 // Set up middlewares. 77 r.Use(middleware.RequestID) 78 r.Use(middleware.RealIP) 79 r.Use(middleware.Logger) 80 r.Use(middleware.Recoverer) 81 82 // Set up Chi's third party libraries. 83 r.Use(httprate.Limit( 84 200, 85 1*time.Minute, 86 httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { 87 res := NewFailureResponse(http.StatusTooManyRequests, "You have performed too many requests! Please try again in a minute!") 88 sendFailureResponse(w, res) 89 }), 90 )) 91 92 // Set up custom middleware. Don't forget to inject dependencies. 93 r.Use(customHeaders) 94 r.Use(httpsRedirect(applicationMode)) 95 96 // Group routes. 97 r.Route("/api/v1", func(r chi.Router) { 98 // Use compression on API. 99 r.Use(middleware.Compress(5)) 100 101 // Sample GET request. 102 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 103 res := NewSuccessResponse(http.StatusOK, "Welcome to 'net/http' API!", nil) 104 sendSuccessResponse(w, res) 105 }) 106 107 // Sample POST request. 108 r.Post("/", func(w http.ResponseWriter, r *http.Request) { 109 input := &inference.Input{} 110 111 // Check input length. 112 r.Body = http.MaxBytesReader(w, r.Body, 10240) 113 114 // Create JSON decoder. 115 decoder := json.NewDecoder(r.Body) 116 decoder.DisallowUnknownFields() 117 118 // Parse JSON. 119 if err := decoder.Decode(input); err != nil { 120 sendFailureResponse(w, NewFailureResponse(http.StatusBadRequest, err.Error())) 121 return 122 } 123 124 // Perform inference with our Expert System based on the given input. 125 inferredData := inference.Infer(input) 126 127 // Send back response. 128 res := NewSuccessResponse(http.StatusOK, "Successfully processed data in the Expert System!", inferredData) 129 sendSuccessResponse(w, res) 130 }) 131 132 // Declare method not allowed as fallback route. 133 r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) { 134 errorMessage := fmt.Sprintf("Method '%s' is not allowed in this route!", r.Method) 135 sendFailureResponse(w, NewFailureResponse(http.StatusMethodNotAllowed, errorMessage)) 136 }) 137 138 // For this, we declare a 404. 139 r.NotFound(func(w http.ResponseWriter, r *http.Request) { 140 errorMessage := fmt.Sprintf("Route '%s' does not exist in this server!", r.RequestURI) 141 sendFailureResponse(w, NewFailureResponse(http.StatusNotFound, errorMessage)) 142 }) 143 }) 144 145 // Fallback route, serve React app. 146 r.Get("/*", func(w http.ResponseWriter, r *http.Request) { 147 fs := http.FileServer(http.Dir(pathToWebDirectory)) 148 149 // If there is no route in the React application, send back 404. 150 // Else, send back React application. 151 if _, err := os.Stat(pathToWebDirectory + r.RequestURI); os.IsNotExist(err) { 152 w.WriteHeader(http.StatusNotFound) 153 http.StripPrefix(r.RequestURI, fs).ServeHTTP(w, r) 154 } else { 155 fs.ServeHTTP(w, r) 156 } 157 }) 158 159 // Returns our router instance. 160 return r 161 }