Hone logo
Hone
Problems

Implementing a Middleware Chain in Go

Middleware chains are a fundamental pattern in web development, allowing you to intercept and process requests before they reach your core application logic. This challenge asks you to implement a basic middleware chain in Go, enabling you to add functionality like logging, authentication, and request modification before the request is handled by the main handler. This is a crucial skill for building robust and maintainable Go applications.

Problem Description

You are tasked with creating a MiddlewareChain that can execute a series of middleware functions in a specific order. Each middleware function should receive the request, and optionally modify it or terminate the chain early. The chain should ultimately invoke a final handler function with the (potentially modified) request.

What needs to be achieved:

  • Create a MiddlewareChain type that can hold a slice of middleware functions.
  • Implement a Handle method on the MiddlewareChain that iterates through the middleware functions, executing them in order.
  • Each middleware function should accept a Request and a Handler (the next middleware or the final handler).
  • Middleware functions can either:
    • Process the request and pass it to the next handler.
    • Process the request and return a response, terminating the chain.
  • The final handler should accept a Request and return a Response.

Key Requirements:

  • The MiddlewareChain must support adding middleware functions.
  • The Handle method must execute the middleware functions in the order they are added.
  • Middleware functions must be able to terminate the chain early by returning a Response.
  • The final handler must be invoked if no middleware terminates the chain.

Expected Behavior:

The Handle method should execute each middleware function sequentially. If a middleware function returns a Response, the chain should terminate immediately, and that response should be returned. If all middleware functions execute without returning a Response, the final handler should be invoked, and its response should be returned.

Edge Cases to Consider:

  • Empty middleware chain: The final handler should be invoked directly.
  • Middleware functions that panic: The chain should handle panics gracefully (e.g., by logging the error and returning a default error response).
  • Nil handler: If the final handler is nil, the chain should return an error.

Examples

Example 1:

Input:
  MiddlewareChain: [MiddlewareFunc1, MiddlewareFunc2]
  Request: {Data: "Initial Request"}
  FinalHandler: func(req Request) Response { return Response{Data: "Final Response"} }

MiddlewareFunc1: func(req Request, next Handler) Response {
  return next(Request{Data: "Middleware 1 processed: " + req.Data})
}

MiddlewareFunc2: func(req Request, next Handler) Response {
  return Response{Data: "Middleware 2 processed: " + req.Data}
}
Output: Response{Data: "Middleware 2 processed: Middleware 1 processed: Initial Request"}
Explanation: MiddlewareFunc1 processes the request and passes it to MiddlewareFunc2. MiddlewareFunc2 processes the request and returns a response, terminating the chain.

Example 2:

Input:
  MiddlewareChain: [MiddlewareFunc1, MiddlewareFunc2]
  Request: {Data: "Initial Request"}
  FinalHandler: func(req Request) Response { return Response{Data: "Final Response"} }

MiddlewareFunc1: func(req Request, next Handler) Response {
  return Response{Data: "Middleware 1 terminated"}
}

MiddlewareFunc2: func(req Request, next Handler) Response {
  return next(req) // This middleware will not be executed
}
Output: Response{Data: "Middleware 1 terminated"}
Explanation: MiddlewareFunc1 returns a response, terminating the chain before MiddlewareFunc2 is executed.

Example 3: (Empty Chain)

Input:
  MiddlewareChain: []
  Request: {Data: "Initial Request"}
  FinalHandler: func(req Request) Response { return Response{Data: "Final Response"} }
Output: Response{Data: "Final Response"}
Explanation: The middleware chain is empty, so the final handler is invoked directly.

Constraints

  • The Request and Response types are defined as follows: type Request struct { Data string } and type Response struct { Data string }.
  • Middleware functions and the final handler must be of type func(Request) Response.
  • The MiddlewareChain must be able to handle at least 10 middleware functions without significant performance degradation.
  • Error handling should be implemented to prevent panics from crashing the application.

Notes

  • Consider using closures to capture the next handler in the chain.
  • Think about how to handle errors gracefully within the middleware chain.
  • The focus is on the core logic of the middleware chain; error handling and logging can be kept simple for this exercise.
  • The Handle method should return both the Response and an error (if any).
  • You can assume that the Request and Response structs are immutable.
Loading editor...
go