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  }