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
MiddlewareChaintype that can hold a slice of middleware functions. - Implement a
Handlemethod on theMiddlewareChainthat iterates through the middleware functions, executing them in order. - Each middleware function should accept a
Requestand aHandler(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
Requestand return aResponse.
Key Requirements:
- The
MiddlewareChainmust support adding middleware functions. - The
Handlemethod 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
RequestandResponsetypes are defined as follows:type Request struct { Data string }andtype Response struct { Data string }. - Middleware functions and the final handler must be of type
func(Request) Response. - The
MiddlewareChainmust 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
Handlemethod should return both theResponseand anerror(if any). - You can assume that the
RequestandResponsestructs are immutable.