Repository: beatlabs/patron
Pull request #636 (2023-06-24 17:02:30)

HTTP v1 deprecation (Breaking Change)
<!--
Thanks for taking precious time for making a PR.

Before creating a pull request, please make sure:
- Your PR solves one problem for which an issue exist and a solution has been discussed
- You have read the guide for contributing
  - See https://github.com/beatlabs/patron/blob/master/README.md#how-to-contribute
- You signed all your commits (otherwise we won't be able to merge the PR)
  - See https://github.com/beatlabs/patron/blob/master/SIGNYOURWORK.md
- You added unit tests for the new functionality
- You mention in the PR description which issue it is addressing, e.g. "Resolves #123"
-->

## Which problem is this PR solving?

Deprecate HTTP v1.

## Short description of the changes

Removed v1 and moved v2 into its place.
Fixed CI and docker compose.
This is a breaking change.



Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index d2833d5a8..b866eb923 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -8,15 +8,15 @@ on:
 jobs:
   lint:
     name: Lint and fmt check
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-latest
     steps:
+      - name: Check out source code
+        uses: actions/checkout@v3
+
       - name: Set up Go
         uses: actions/setup-go@v4
         with:
-          go-version: 1.19
-
-      - name: Check out source code
-        uses: actions/checkout@v3
+          go-version-file: 'go.mod'
 
       - name: Linting and fmt check
         run: make lint
@@ -25,7 +25,7 @@ jobs:
     name: CI on Go ${{ matrix.go-image-version}}
     strategy:
       matrix:
-        go-image-version: ["1.19", "1.20"]
+        go-image-version: ["1.20"]
     runs-on: ubuntu-latest
     steps:
       - name: Set up Go ${{ matrix.go-image-version }}
diff --git a/README.md b/README.md
index 233a42670..14843730b 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ The entry point of the framework is the `Service`. The `Service` uses `Component
 - logging
 
 `Patron` provides the same defaults for making the usage as simple as possible.
-`Patron` needs Go 1.19 as a minimum.
+`Patron` needs Go 1.20 as a minimum.
 
 ## Table of Contents
 
diff --git a/component/async/kafka/simple/simple.go b/component/async/kafka/simple/simple.go
index 27d0a55b4..07b18e97d 100644
--- a/component/async/kafka/simple/simple.go
+++ b/component/async/kafka/simple/simple.go
@@ -16,7 +16,7 @@ import (
 )
 
 // unixNanoToTimestampDivider divides unix nano seconds to valid timestamp for kafka messages.
-const unixNanoToTimestampDivider = 1000_000
+const unixNanoToTimestampDivider = 1000000
 
 // TimeExtractor defines a function extracting a time from a Kafka message.
 type TimeExtractor func(*sarama.ConsumerMessage) (time.Time, error)
diff --git a/component/http/alive.go b/component/http/alive.go
deleted file mode 100644
index 76ee0f56a..000000000
--- a/component/http/alive.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package http
-
-import (
-	"net/http"
-)
-
-// AliveStatus type representing the liveness of the service via HTTP component.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type AliveStatus int
-
-const (
-	// Alive represents a state defining an Alive state.
-	Alive AliveStatus = 1
-	// Unresponsive represents a state defining a Unresponsive state.
-	Unresponsive AliveStatus = 2
-
-	// AlivePath of the service.
-	AlivePath = "/alive"
-)
-
-// AliveCheckFunc defines a function type for implementing a liveness check.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type AliveCheckFunc func() AliveStatus
-
-func aliveCheckRoute(acf AliveCheckFunc) *RouteBuilder {
-	f := func(w http.ResponseWriter, r *http.Request) {
-		switch acf() {
-		case Alive:
-			w.WriteHeader(http.StatusOK)
-		case Unresponsive:
-			w.WriteHeader(http.StatusServiceUnavailable)
-		default:
-			w.WriteHeader(http.StatusOK)
-		}
-	}
-	return NewRawRouteBuilder(AlivePath, f).MethodGet()
-}
diff --git a/component/http/v2/check.go b/component/http/check.go
similarity index 99%
rename from component/http/v2/check.go
rename to component/http/check.go
index b5a221b0a..c8f203f44 100644
--- a/component/http/v2/check.go
+++ b/component/http/check.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"net/http"
diff --git a/component/http/component.go b/component/http/component.go
index 3da7d0c34..72fc40f09 100644
--- a/component/http/component.go
+++ b/component/http/component.go
@@ -1,3 +1,4 @@
+// Package v2 provides a ready to use HTTP component.
 package http
 
 import (
@@ -10,9 +11,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/beatlabs/patron/component/http/middleware"
-	patronErrors "github.com/beatlabs/patron/errors"
-	"github.com/julienschmidt/httprouter"
 	"golang.org/x/exp/slog"
 )
 
@@ -21,101 +19,10 @@ const (
 	defaultReadTimeout         = 30 * time.Second
 	defaultWriteTimeout        = 60 * time.Second
 	defaultIdleTimeout         = 240 * time.Second
+	defaultHandlerTimeout      = 59 * time.Second // should be smaller than write timeout
 	defaultShutdownGracePeriod = 5 * time.Second
-	defaultDeflateLevel        = 6
 )
 
-var (
-	// DefaultAliveCheck return always live.
-	//
-	// Deprecated: Please use the new v2 package.
-	// This package is frozen and no new functionality will be added.
-	DefaultAliveCheck = func() AliveStatus { return Alive }
-	// DefaultReadyCheck return always ready.
-	//
-	// Deprecated: Please use the new v2 package.
-	// This package is frozen and no new functionality will be added.
-	DefaultReadyCheck = func() ReadyStatus { return Ready }
-)
-
-// Component implementation of HTTP.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type Component struct {
-	ac                  AliveCheckFunc
-	rc                  ReadyCheckFunc
-	httpPort            int
-	httpReadTimeout     time.Duration
-	httpWriteTimeout    time.Duration
-	deflateLevel        int
-	uncompressedPaths   []string
-	shutdownGracePeriod time.Duration
-	sync.Mutex
-	routes      []Route
-	middlewares []middleware.Func
-	certFile    string
-	keyFile     string
-}
-
-// Run starts the HTTP server.
-func (c *Component) Run(ctx context.Context) error {
-	c.Lock()
-	slog.Debug("applying tracing to routes")
-	chFail := make(chan error)
-	srv := c.createHTTPServer()
-	go c.listenAndServe(srv, chFail)
-	c.Unlock()
-
-	select {
-	case <-ctx.Done():
-		slog.Info("shutting down HTTP component")
-		tctx, cancel := context.WithTimeout(context.Background(), c.shutdownGracePeriod)
-		defer cancel()
-		return srv.Shutdown(tctx)
-	case err := <-chFail:
-		return err
-	}
-}
-
-func (c *Component) listenAndServe(srv *http.Server, ch chan<- error) {
-	if c.certFile != "" && c.keyFile != "" {
-		slog.Debug("HTTPS component listening", slog.Int("port", c.httpPort))
-		ch <- srv.ListenAndServeTLS(c.certFile, c.keyFile)
-	}
-
-	slog.Debug("HTTP component listening", slog.Int("port", c.httpPort))
-	ch <- srv.ListenAndServe()
-}
-
-func (c *Component) createHTTPServer() *http.Server {
-	slog.Debug("adding routes", slog.Int("routes", len(c.routes)))
-	router := httprouter.New()
-	for _, route := range c.routes {
-		if len(route.middlewares) > 0 {
-			h := middleware.Chain(route.handler, route.middlewares...)
-			router.Handler(route.method, route.path, h)
-		} else {
-			router.HandlerFunc(route.method, route.path, route.handler)
-		}
-
-		slog.Debug("added route", slog.String("method", route.method), slog.String("path", route.path))
-	}
-	// Add first the recovery middleware to ensure that no panic occur.
-	routerAfterMiddleware := middleware.Chain(router, middleware.NewRecovery())
-	compressionMiddleware, _ := middleware.NewCompression(c.deflateLevel, c.uncompressedPaths...)
-	c.middlewares = append(c.middlewares, compressionMiddleware)
-	routerAfterMiddleware = middleware.Chain(routerAfterMiddleware, c.middlewares...)
-
-	return &http.Server{
-		Addr:         fmt.Sprintf(":%d", c.httpPort),
-		ReadTimeout:  c.httpReadTimeout,
-		WriteTimeout: c.httpWriteTimeout,
-		IdleTimeout:  defaultIdleTimeout,
-		Handler:      routerAfterMiddleware,
-	}
-}
-
 func port() (int, error) {
 	port, ok := os.LookupEnv("PATRON_HTTP_DEFAULT_PORT")
 	if !ok {
@@ -126,7 +33,7 @@ func port() (int, error) {
 	if err != nil {
 		return 0, fmt.Errorf("env var for HTTP default port is not valid: %w", err)
 	}
-	slog.Debug("using port", slog.Int("port", int(portVal)))
+	slog.Debug("using port", slog.Int64("port", portVal))
 	return int(portVal), nil
 }
 
@@ -158,249 +65,95 @@ func writeTimeout() (time.Duration, error) {
 	return timeout, nil
 }
 
-func deflateLevel() (int, error) {
-	deflateLevel, ok := os.LookupEnv("PATRON_COMPRESSION_DEFLATE_LEVEL")
-	if !ok {
-		slog.Debug("using default deflate level", slog.Int("level", defaultDeflateLevel))
-		return defaultDeflateLevel, nil
-	}
-	deflateLevelInt, err := strconv.Atoi(deflateLevel)
-	if err != nil {
-		return 0, fmt.Errorf("env var for HTTP deflate level is not valid: %w", err)
-	}
-	slog.Debug("using deflate level", slog.Int("level", deflateLevelInt))
-	return deflateLevelInt, nil
-}
-
-// Builder gathers all required and optional properties, in order
-// to construct an HTTP component.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type Builder struct {
-	ac                  AliveCheckFunc
-	rc                  ReadyCheckFunc
-	httpPort            int
-	httpReadTimeout     time.Duration
-	httpWriteTimeout    time.Duration
-	deflateLevel        int
-	uncompressedPaths   []string
+// Component implementation of an HTTP router.
+type Component struct {
+	port                int
+	readTimeout         time.Duration
+	writeTimeout        time.Duration
 	shutdownGracePeriod time.Duration
-	routesBuilder       *RoutesBuilder
-	middlewares         []middleware.Func
+	handlerTimeout      time.Duration
+	handler             http.Handler
+	mu                  sync.Mutex
 	certFile            string
 	keyFile             string
-	errors              []error
 }
 
-// NewBuilder initiates the HTTP component builder chain.
-// The builder instantiates the component using default values for
-// HTTP Port, Alive/Ready check functions and Read/Write timeouts.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewBuilder() *Builder {
-	var ee []error
+// New creates an HTTP component configurable by functional options.
+func New(handler http.Handler, oo ...OptionFunc) (*Component, error) {
+	if handler == nil {
+		return nil, errors.New("handler is nil")
+	}
 
 	port, err := port()
 	if err != nil {
-		ee = append(ee, err)
+		return nil, err
 	}
 
 	readTimeout, err := readTimeout()
 	if err != nil {
-		ee = append(ee, err)
+		return nil, err
 	}
 
 	writeTimeout, err := writeTimeout()
 	if err != nil {
-		ee = append(ee, err)
-	}
-
-	deflateLevel, err := deflateLevel()
-	if err != nil {
-		ee = append(ee, err)
+		return nil, err
 	}
 
-	return &Builder{
-		ac:                  DefaultAliveCheck,
-		rc:                  DefaultReadyCheck,
-		httpPort:            port,
-		httpReadTimeout:     readTimeout,
-		httpWriteTimeout:    writeTimeout,
-		deflateLevel:        deflateLevel,
-		uncompressedPaths:   []string{MetricsPath, AlivePath, ReadyPath},
+	cmp := &Component{
+		port:                port,
+		readTimeout:         readTimeout,
+		writeTimeout:        writeTimeout,
 		shutdownGracePeriod: defaultShutdownGracePeriod,
-		routesBuilder:       NewRoutesBuilder(),
-		errors:              ee,
+		handlerTimeout:      defaultHandlerTimeout,
+		handler:             handler,
 	}
-}
 
-// WithSSL sets the filenames for the Certificate and Keyfile, in order to enable SSL.
-func (cb *Builder) WithSSL(c, k string) *Builder {
-	if c == "" || k == "" {
-		cb.errors = append(cb.errors, errors.New("invalid cert or key provided"))
-	} else {
-		slog.Debug("setting cert file and key")
-		cb.certFile = c
-		cb.keyFile = k
-	}
-
-	return cb
-}
-
-// WithRoutesBuilder adds routes builder to the HTTP component.
-func (cb *Builder) WithRoutesBuilder(rb *RoutesBuilder) *Builder {
-	if rb == nil {
-		cb.errors = append(cb.errors, errors.New("route builder is nil"))
-	} else {
-		slog.Debug("setting route builder")
-		cb.routesBuilder = rb
-	}
-	return cb
-}
-
-// WithMiddlewares adds middlewares to the HTTP component.
-func (cb *Builder) WithMiddlewares(mm ...middleware.Func) *Builder {
-	if len(mm) == 0 {
-		cb.errors = append(cb.errors, errors.New("empty list of middlewares provided"))
-	} else {
-		slog.Debug("setting middlewares")
-		cb.middlewares = append(cb.middlewares, mm...)
-	}
-
-	return cb
-}
-
-// WithReadTimeout sets the Read WithTimeout for the HTTP component.
-func (cb *Builder) WithReadTimeout(rt time.Duration) *Builder {
-	if rt <= 0*time.Second {
-		cb.errors = append(cb.errors, errors.New("negative or zero read timeout provided"))
-	} else {
-		slog.Debug("setting read timeout")
-		cb.httpReadTimeout = rt
-	}
-
-	return cb
-}
-
-// WithWriteTimeout sets write timeout for the HTTP component.
-func (cb *Builder) WithWriteTimeout(wt time.Duration) *Builder {
-	if wt <= 0*time.Second {
-		cb.errors = append(cb.errors, errors.New("negative or zero write timeout provided"))
-	} else {
-		slog.Debug("setting write timeout")
-		cb.httpWriteTimeout = wt
-	}
-
-	return cb
-}
-
-// WithDeflateLevel sets the level of compression for Deflate; based on https://golang.org/pkg/compress/flate/
-// Levels range from 1 (BestSpeed) to 9 (BestCompression); higher levels typically run slower but compress more.
-// Level 0 (NoCompression) does not attempt any compression; it only adds the necessary DEFLATE framing.
-// Level -1 (DefaultCompression) uses the default compression level.
-// Level -2 (HuffmanOnly) will use Huffman compression only, giving a very fast compression for all types of input, but sacrificing considerable compression efficiency.
-func (cb *Builder) WithDeflateLevel(level int) *Builder {
-	if level < -2 || level > 9 {
-		cb.errors = append(cb.errors, errors.New("provided deflate level value not in the [-2, 9] range"))
-	} else {
-		cb.deflateLevel = level
-	}
-	return cb
-}
-
-// WithUncompressedPaths specifies which routes should be excluded from compression
-// Any trailing slashes are trimmed, so we match both /metrics/ and /metrics?seconds=30.
-func (cb *Builder) WithUncompressedPaths(r ...string) *Builder {
-	res := make([]string, 0, len(r))
-	for _, e := range r {
-		for len(e) > 1 && e[len(e)-1] == '/' {
-			e = e[0 : len(e)-1]
+	for _, option := range oo {
+		err := option(cmp)
+		if err != nil {
+			return nil, err
 		}
-		res = append(res, e)
 	}
-	cb.uncompressedPaths = append(cb.uncompressedPaths, res...)
 
-	return cb
+	return cmp, nil
 }
 
-// WithShutdownGracePeriod sets the Shutdown Grace Period for the HTTP component.
-func (cb *Builder) WithShutdownGracePeriod(gp time.Duration) *Builder {
-	if gp <= 0*time.Second {
-		cb.errors = append(cb.errors, errors.New("negative or zero shutdown grace period provided"))
-	} else {
-		slog.Debug("setting shutdown grace period")
-		cb.shutdownGracePeriod = gp
-	}
-
-	return cb
-}
-
-// WithPort sets the port used by the HTTP component.
-func (cb *Builder) WithPort(p int) *Builder {
-	if p <= 0 || p > 65535 {
-		cb.errors = append(cb.errors, errors.New("invalid HTTP Port provided"))
-	} else {
-		slog.Debug("setting port")
-		cb.httpPort = p
-	}
-
-	return cb
-}
+// Run starts the HTTP server and returns only if listening and/or serving failed, or if the context was canceled.
+func (c *Component) Run(ctx context.Context) error {
+	c.mu.Lock()
+	chFail := make(chan error)
+	srv := c.createHTTPServer()
+	go c.listenAndServe(srv, chFail)
+	c.mu.Unlock()
 
-// WithAliveCheckFunc sets the AliveCheckFunc used by the HTTP component.
-func (cb *Builder) WithAliveCheckFunc(acf AliveCheckFunc) *Builder {
-	if acf == nil {
-		cb.errors = append(cb.errors, errors.New("nil AliveCheckFunc was provided"))
-	} else {
-		slog.Debug("setting aliveness check")
-		cb.ac = acf
+	select {
+	case <-ctx.Done():
+		slog.Info("shutting down HTTP component")
+		ctx, cancel := context.WithTimeout(context.Background(), c.shutdownGracePeriod)
+		defer cancel()
+		return srv.Shutdown(ctx)
+	case err := <-chFail:
+		return err
 	}
-
-	return cb
 }
 
-// WithReadyCheckFunc sets the ReadyCheckFunc used by the HTTP component.
-func (cb *Builder) WithReadyCheckFunc(rcf ReadyCheckFunc) *Builder {
-	if rcf == nil {
-		cb.errors = append(cb.errors, errors.New("nil ReadyCheckFunc provided"))
-	} else {
-		slog.Debug("setting readiness check")
-		cb.rc = rcf
+func (c *Component) createHTTPServer() *http.Server {
+	return &http.Server{
+		Addr:         fmt.Sprintf(":%d", c.port),
+		ReadTimeout:  c.readTimeout,
+		WriteTimeout: c.writeTimeout,
+		IdleTimeout:  defaultIdleTimeout,
+		Handler:      http.TimeoutHandler(c.handler, c.handlerTimeout, ""),
 	}
-
-	return cb
 }
 
-// Create constructs the HTTP component by applying the gathered properties.
-func (cb *Builder) Create() (*Component, error) {
-	if len(cb.errors) > 0 {
-		return nil, patronErrors.Aggregate(cb.errors...)
-	}
-
-	for _, rb := range profilingRoutes() {
-		cb.routesBuilder.Append(rb)
-	}
-
-	routes, err := cb.routesBuilder.Append(aliveCheckRoute(cb.ac)).Append(readyCheckRoute(cb.rc)).
-		Append(metricRoute()).Build()
-	if err != nil {
-		return nil, err
+func (c *Component) listenAndServe(srv *http.Server, ch chan<- error) {
+	if c.certFile != "" && c.keyFile != "" {
+		slog.Debug("HTTPS component listening", slog.Int("port", c.port))
+		ch <- srv.ListenAndServeTLS(c.certFile, c.keyFile)
+		return
 	}
 
-	return &Component{
-		ac:                  cb.ac,
-		rc:                  cb.rc,
-		httpPort:            cb.httpPort,
-		httpReadTimeout:     cb.httpReadTimeout,
-		httpWriteTimeout:    cb.httpWriteTimeout,
-		deflateLevel:        cb.deflateLevel,
-		uncompressedPaths:   cb.uncompressedPaths,
-		shutdownGracePeriod: cb.shutdownGracePeriod,
-		routes:              routes,
-		middlewares:         cb.middlewares,
-		certFile:            cb.certFile,
-		keyFile:             cb.keyFile,
-	}, nil
+	slog.Debug("HTTP component listening", slog.Int("port", c.port))
+	ch <- srv.ListenAndServe()
 }
diff --git a/component/http/v2/component_option.go b/component/http/component_option.go
similarity index 99%
rename from component/http/v2/component_option.go
rename to component/http/component_option.go
index 07f7d9d5f..b2f0da5eb 100644
--- a/component/http/v2/component_option.go
+++ b/component/http/component_option.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"errors"
diff --git a/component/http/errors.go b/component/http/errors.go
deleted file mode 100644
index 9eb557b7b..000000000
--- a/component/http/errors.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package http
-
-import (
-	"fmt"
-	"net/http"
-)
-
-// Error defines an abstract struct that can represent several types of HTTP errors.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type Error struct {
-	code    int
-	payload interface{}
-	headers map[string]string
-}
-
-// Error returns the actual message of the error.
-func (e *Error) Error() string {
-	if e.payload == nil {
-		return fmt.Sprintf("HTTP error with code: %d", e.code)
-	}
-	return fmt.Sprintf("HTTP error with code: %d payload: %v", e.code, e.payload)
-}
-
-// WithHeaders adds headers to the error which will be added to the http response.
-func (e *Error) WithHeaders(headers map[string]string) *Error {
-	e.headers = headers
-	return e
-}
-
-// NewValidationError creates a new validation error with default payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewValidationError() *Error {
-	return &Error{code: http.StatusBadRequest, payload: http.StatusText(http.StatusBadRequest)}
-}
-
-// NewValidationErrorWithPayload creates a new validation error with the specified payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewValidationErrorWithPayload(payload interface{}) *Error {
-	return &Error{code: http.StatusBadRequest, payload: payload}
-}
-
-// NewUnauthorizedError creates a new validation error with default payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewUnauthorizedError() *Error {
-	return &Error{code: http.StatusUnauthorized, payload: http.StatusText(http.StatusUnauthorized)}
-}
-
-// NewUnauthorizedErrorWithPayload creates a new unauthorized error with the specified payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewUnauthorizedErrorWithPayload(payload interface{}) *Error {
-	return &Error{code: http.StatusUnauthorized, payload: payload}
-}
-
-// NewForbiddenError creates a new forbidden error with default payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewForbiddenError() *Error {
-	return &Error{code: http.StatusForbidden, payload: http.StatusText(http.StatusForbidden)}
-}
-
-// NewForbiddenErrorWithPayload creates a new forbidden error with the specified payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewForbiddenErrorWithPayload(payload interface{}) *Error {
-	return &Error{code: http.StatusForbidden, payload: payload}
-}
-
-// NewNotFoundError creates a new not found error with default payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewNotFoundError() *Error {
-	return &Error{code: http.StatusNotFound, payload: http.StatusText(http.StatusNotFound)}
-}
-
-// NewNotFoundErrorWithPayload creates a new not found error with the specified payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewNotFoundErrorWithPayload(payload interface{}) *Error {
-	return &Error{code: http.StatusNotFound, payload: payload}
-}
-
-// NewServiceUnavailableError creates a new service unavailable error with default payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewServiceUnavailableError() *Error {
-	return &Error{code: http.StatusServiceUnavailable, payload: http.StatusText(http.StatusServiceUnavailable)}
-}
-
-// NewServiceUnavailableErrorWithPayload creates a new service unavailable error with the specified payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewServiceUnavailableErrorWithPayload(payload interface{}) *Error {
-	return &Error{code: http.StatusServiceUnavailable, payload: payload}
-}
-
-// NewError creates a new error with default Internal Server Error payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewError() *Error {
-	return &Error{code: http.StatusInternalServerError, payload: http.StatusText(http.StatusInternalServerError)}
-}
-
-// NewErrorWithCodeAndPayload creates a fully customizable error with the specified status code and payload.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewErrorWithCodeAndPayload(code int, payload interface{}) *Error {
-	return &Error{code: code, payload: payload}
-}
diff --git a/component/http/handler.go b/component/http/handler.go
deleted file mode 100644
index c0f4d80e5..000000000
--- a/component/http/handler.go
+++ /dev/null
@@ -1,242 +0,0 @@
-package http
-
-import (
-	"errors"
-	"net/http"
-	"strings"
-
-	"github.com/beatlabs/patron/correlation"
-	"github.com/beatlabs/patron/encoding"
-	"github.com/beatlabs/patron/encoding/json"
-	"github.com/beatlabs/patron/encoding/protobuf"
-	"github.com/beatlabs/patron/log"
-	"github.com/julienschmidt/httprouter"
-	"golang.org/x/exp/slog"
-)
-
-func handler(hnd ProcessorFunc) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		ct, dec, enc, err := determineEncoding(r.Header)
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusUnsupportedMediaType), http.StatusUnsupportedMediaType)
-			return
-		}
-		prepareResponse(w, ct)
-
-		f := extractFields(r)
-		for k, v := range ExtractParams(r) {
-			f[k] = v
-		}
-
-		// TODO : for cached responses this becomes inconsistent, to be fixed in #160
-		// the corID will be passed to all consecutive responses
-		// if it was missing from the initial request
-		corID := getOrSetCorrelationID(r.Header)
-		ctx := correlation.ContextWithID(r.Context(), corID)
-		logger := slog.With(slog.String(correlation.ID, corID))
-		ctx = log.WithContext(ctx, logger)
-
-		h := extractHeaders(r.Header)
-
-		req := NewRequest(f, r.Body, h, dec)
-
-		rsp, err := hnd(ctx, req)
-		if err != nil {
-			handleError(logger, w, enc, err)
-			return
-		}
-
-		err = handleSuccess(w, r, rsp, enc)
-		if err != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-		}
-	}
-}
-
-func getOrSetCorrelationID(h http.Header) string {
-	cor, ok := h[correlation.HeaderID]
-	if !ok {
-		corID := correlation.New()
-		h.Set(correlation.HeaderID, corID)
-		return corID
-	}
-	if len(cor) == 0 {
-		corID := correlation.New()
-		h.Set(correlation.HeaderID, corID)
-		return corID
-	}
-	if cor[0] == "" {
-		corID := correlation.New()
-		h.Set(correlation.HeaderID, corID)
-		return corID
-	}
-	return cor[0]
-}
-
-func determineEncoding(h http.Header) (string, encoding.DecodeFunc, encoding.EncodeFunc, error) {
-	cth, cok := h[encoding.ContentTypeHeader]
-	ach, aok := h[encoding.AcceptHeader]
-
-	// No headers default to JSON
-	if !cok && !aok {
-		return json.TypeCharset, json.Decode, json.Encode, nil
-	}
-
-	var enc encoding.EncodeFunc
-	var dec encoding.DecodeFunc
-	var ct string
-
-	if cok {
-		switch cth[0] {
-		case "*/*", json.Type, json.TypeCharset:
-			enc = json.Encode
-			dec = json.Decode
-			ct = json.TypeCharset
-		case protobuf.Type, protobuf.TypeGoogle:
-			enc = protobuf.Encode
-			dec = protobuf.Decode
-			ct = protobuf.Type
-		default:
-			return "", nil, nil, errors.New("content type Header not supported")
-		}
-	}
-
-	if aok {
-		var err error
-		encFound := false
-
-		ah := getMultiValueHeaders(ach[0])
-		for _, v := range ah {
-			ct, dec, enc, err = getSingleHeaderEncoding(v)
-			if err == nil {
-				encFound = true
-				break
-			}
-		}
-		// No valid headers found after going through all headers
-		if !encFound {
-			return "", nil, nil, errors.New("accept header not supported")
-		}
-	}
-
-	return ct, dec, enc, nil
-}
-
-func getSingleHeaderEncoding(header string) (string, encoding.DecodeFunc, encoding.EncodeFunc, error) {
-	var enc encoding.EncodeFunc
-	var dec encoding.DecodeFunc
-	var ct string
-
-	parts := strings.SplitN(header, ";", 2)
-	switch parts[0] {
-	case "*/*", "*", "identity", json.Type, json.TypeCharset:
-		enc = json.Encode
-		dec = json.Decode
-		ct = json.TypeCharset
-	case protobuf.Type, protobuf.TypeGoogle:
-		enc = protobuf.Encode
-		dec = protobuf.Decode
-		ct = protobuf.Type
-	default:
-		return "", nil, nil, errors.New("accept header not supported")
-	}
-
-	return ct, dec, enc, nil
-}
-
-func getMultiValueHeaders(header string) []string {
-	if !strings.Contains(header, ",") {
-		return []string{header}
-	}
-
-	splitHeaders := strings.Split(header, ",")
-
-	trimmedHeaders := make([]string, 0, len(splitHeaders))
-	for _, v := range splitHeaders {
-		trimmedHeaders = append(trimmedHeaders, strings.TrimSpace(v))
-	}
-
-	return trimmedHeaders
-}
-
-func extractFields(r *http.Request) map[string]string {
-	f := make(map[string]string)
-
-	for name, values := range r.URL.Query() {
-		f[name] = values[0]
-	}
-	return f
-}
-
-func extractHeaders(header http.Header) Header {
-	h := make(map[string]string)
-
-	for name, values := range header {
-		for _, value := range values {
-			if len(value) > 0 {
-				h[strings.ToUpper(name)] = value
-			}
-		}
-	}
-	return h
-}
-
-func handleSuccess(w http.ResponseWriter, r *http.Request, rsp *Response, enc encoding.EncodeFunc) error {
-	if rsp == nil {
-		w.WriteHeader(http.StatusNoContent)
-		return nil
-	}
-
-	p, err := enc(rsp.Payload)
-	if err != nil {
-		return err
-	}
-
-	if r.Method == http.MethodPost {
-		w.WriteHeader(http.StatusCreated)
-	}
-
-	propagateHeaders(rsp.Header, w.Header())
-
-	_, err = w.Write(p)
-	return err
-}
-
-func handleError(logger *slog.Logger, w http.ResponseWriter, enc encoding.EncodeFunc, err error) {
-	var errAs *Error
-	if errors.As(err, &errAs) {
-		p, encErr := enc(errAs.payload)
-		if encErr != nil {
-			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-			return
-		}
-		for k, v := range errAs.headers {
-			w.Header().Set(k, v)
-		}
-		w.WriteHeader(errAs.code)
-		if _, err := w.Write(p); err != nil {
-			logger.Error("failed to write Response", slog.Any("error", err))
-		}
-		return
-	}
-
-	// Using http.Error helper hijacks the content type Header of the Response returning plain text Payload.
-	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-}
-
-func prepareResponse(w http.ResponseWriter, ct string) {
-	w.Header().Set(encoding.ContentTypeHeader, ct)
-}
-
-// ExtractParams extracts dynamic URL parameters using httprouter's functionality.
-func ExtractParams(r *http.Request) map[string]string {
-	par := httprouter.ParamsFromContext(r.Context())
-	if len(par) == 0 {
-		return make(map[string]string)
-	}
-	p := make(map[string]string)
-	for _, v := range par {
-		p[v.Key] = v.Value
-	}
-	return p
-}
diff --git a/component/http/http.go b/component/http/http.go
deleted file mode 100644
index ae985b875..000000000
--- a/component/http/http.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Package http provides an HTTP component with included observability.
-package http
-
-import (
-	"context"
-	"io"
-	"net/http"
-
-	"github.com/beatlabs/patron/encoding"
-)
-
-// Header is the http header representation as a map of strings
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type Header map[string]string
-
-// Request definition of the sync request model.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type Request struct {
-	Fields  map[string]string
-	Raw     io.Reader
-	Headers Header
-	decode  encoding.DecodeFunc
-}
-
-// NewRequest creates a new request.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewRequest(f map[string]string, r io.Reader, h map[string]string, d encoding.DecodeFunc) *Request {
-	return &Request{Fields: f, Raw: r, Headers: h, decode: d}
-}
-
-// Decode the raw data by using the provided decoder.
-func (r *Request) Decode(v interface{}) error {
-	return r.decode(r.Raw, v)
-}
-
-// Response definition of the sync Response model.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type Response struct {
-	Payload interface{}
-	Header  Header
-}
-
-// NewResponse creates a new Response.
-func NewResponse(p interface{}) *Response {
-	return &Response{Payload: p, Header: make(map[string]string)}
-}
-
-// ProcessorFunc definition of a function type for processing sync requests.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type ProcessorFunc func(context.Context, *Request) (*Response, error)
-
-func propagateHeaders(header Header, wHeader http.Header) {
-	for k, h := range header {
-		wHeader.Set(k, h)
-	}
-}
diff --git a/component/http/metric.go b/component/http/metric.go
deleted file mode 100644
index 39565480c..000000000
--- a/component/http/metric.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package http
-
-import (
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/promhttp"
-)
-
-// MetricsPath definition.
-const MetricsPath = "/metrics"
-
-func metricRoute() *RouteBuilder {
-	return NewRawRouteBuilder(MetricsPath, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}).ServeHTTP).MethodGet()
-}
diff --git a/component/http/v2/observability.go b/component/http/observability.go
similarity index 99%
rename from component/http/v2/observability.go
rename to component/http/observability.go
index 84aa665d1..5aedb67ed 100644
--- a/component/http/v2/observability.go
+++ b/component/http/observability.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"expvar"
diff --git a/component/http/profiling.go b/component/http/profiling.go
deleted file mode 100644
index 564e0b4dd..000000000
--- a/component/http/profiling.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package http
-
-import (
-	"net/http"
-	"net/http/pprof"
-)
-
-func profilingRoutes() []*RouteBuilder {
-	return []*RouteBuilder{
-		NewRawRouteBuilder("/debug/pprof/", profIndex).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/allocs/", pprofAllocsIndex).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/cmdline/", profCmdline).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/profile/", profProfile).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/symbol/", profSymbol).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/trace/", profTrace).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/heap/", profHeap).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/goroutine/", profGoroutine).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/block/", profBlock).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/threadcreate/", profThreadcreate).MethodGet(),
-		NewRawRouteBuilder("/debug/pprof/mutex/", profMutex).MethodGet(),
-	}
-}
-
-func profIndex(w http.ResponseWriter, r *http.Request) {
-	pprof.Index(w, r)
-}
-
-func pprofAllocsIndex(w http.ResponseWriter, r *http.Request) {
-	pprof.Handler("allocs").ServeHTTP(w, r)
-}
-
-func profCmdline(w http.ResponseWriter, r *http.Request) {
-	pprof.Cmdline(w, r)
-}
-
-func profProfile(w http.ResponseWriter, r *http.Request) {
-	pprof.Profile(w, r)
-}
-
-func profSymbol(w http.ResponseWriter, r *http.Request) {
-	pprof.Symbol(w, r)
-}
-
-func profTrace(w http.ResponseWriter, r *http.Request) {
-	pprof.Trace(w, r)
-}
-
-func profHeap(w http.ResponseWriter, r *http.Request) {
-	pprof.Handler("heap").ServeHTTP(w, r)
-}
-
-func profGoroutine(w http.ResponseWriter, r *http.Request) {
-	pprof.Handler("goroutine").ServeHTTP(w, r)
-}
-
-func profBlock(w http.ResponseWriter, r *http.Request) {
-	pprof.Handler("block").ServeHTTP(w, r)
-}
-
-func profThreadcreate(w http.ResponseWriter, r *http.Request) {
-	pprof.Handler("threadcreate").ServeHTTP(w, r)
-}
-
-func profMutex(w http.ResponseWriter, r *http.Request) {
-	pprof.Handler("mutex").ServeHTTP(w, r)
-}
diff --git a/component/http/ready.go b/component/http/ready.go
deleted file mode 100644
index 3de667291..000000000
--- a/component/http/ready.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package http
-
-import (
-	"net/http"
-)
-
-// ReadyStatus type.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type ReadyStatus int
-
-const (
-	// Ready represents a state defining a Ready state.
-	Ready ReadyStatus = 1
-	// NotReady represents a state defining a NotReady state.
-	NotReady ReadyStatus = 2
-
-	// ReadyPath of the service.
-	ReadyPath = "/ready"
-)
-
-// ReadyCheckFunc defines a function type for implementing a readiness check.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type ReadyCheckFunc func() ReadyStatus
-
-func readyCheckRoute(rcf ReadyCheckFunc) *RouteBuilder {
-	f := func(w http.ResponseWriter, r *http.Request) {
-		switch rcf() {
-		case Ready:
-			w.WriteHeader(http.StatusOK)
-		case NotReady:
-			w.WriteHeader(http.StatusServiceUnavailable)
-		default:
-			w.WriteHeader(http.StatusOK)
-		}
-	}
-	return NewRawRouteBuilder(ReadyPath, f).MethodGet()
-}
diff --git a/component/http/route.go b/component/http/route.go
index 00e2e77e1..a253bbf32 100644
--- a/component/http/route.go
+++ b/component/http/route.go
@@ -2,436 +2,138 @@ package http
 
 import (
 	"errors"
-	"fmt"
 	"net/http"
-	"os"
-	"strings"
 
-	"github.com/beatlabs/patron/cache"
-	"github.com/beatlabs/patron/component/http/auth"
-	httpcache "github.com/beatlabs/patron/component/http/cache"
-	"github.com/beatlabs/patron/component/http/middleware"
-	errs "github.com/beatlabs/patron/errors"
-	"golang.org/x/time/rate"
+	patronhttp "github.com/beatlabs/patron/component/http/middleware"
+	patronerrors "github.com/beatlabs/patron/errors"
 )
 
+// RouteOptionFunc definition for configuring the route in a functional way.
+type RouteOptionFunc func(route *Route) error
+
 // Route definition of an HTTP route.
 type Route struct {
-	path        string
 	method      string
+	path        string
 	handler     http.HandlerFunc
-	middlewares []middleware.Func
-}
-
-// Path returns route path value.
-func (r Route) Path() string {
-	return r.path
+	middlewares []patronhttp.Func
 }
 
-// Method returns route method value (GET/POST/...).
 func (r Route) Method() string {
 	return r.method
 }
 
-// Middlewares returns route middlewares.
-func (r Route) Middlewares() []middleware.Func {
-	return r.middlewares
+func (r Route) Path() string {
+	return r.path
 }
 
-// Handler returns route handler function.
 func (r Route) Handler() http.HandlerFunc {
 	return r.handler
 }
 
-// RouteBuilder for building a route.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type RouteBuilder struct {
-	method        string
-	path          string
-	jaegerTrace   bool
-	rateLimiter   *rate.Limiter
-	middlewares   []middleware.Func
-	authenticator auth.Authenticator
-	handler       http.HandlerFunc
-	routeCache    *httpcache.RouteCache
-	errors        []error
-}
-
-// WithTrace enables route tracing that uses Jaeger/OpenTracing.
-// It requires Jaeger enabled on the Patron service.
-func (rb *RouteBuilder) WithTrace() *RouteBuilder {
-	rb.jaegerTrace = true
-	return rb
-}
-
-// WithRateLimiting enables route rate limiting.
-func (rb *RouteBuilder) WithRateLimiting(limit float64, burst int) *RouteBuilder {
-	rb.rateLimiter = rate.NewLimiter(rate.Limit(limit), burst)
-	return rb
-}
-
-// WithMiddlewares adds middlewares.
-func (rb *RouteBuilder) WithMiddlewares(mm ...middleware.Func) *RouteBuilder {
-	if len(mm) == 0 {
-		rb.errors = append(rb.errors, errors.New("middlewares are empty"))
-	}
-	rb.middlewares = mm
-	return rb
-}
-
-// WithAuth adds authenticator.
-func (rb *RouteBuilder) WithAuth(auth auth.Authenticator) *RouteBuilder {
-	if auth == nil {
-		rb.errors = append(rb.errors, errors.New("authenticator is nil"))
-	}
-	rb.authenticator = auth
-	return rb
-}
-
-// WithRouteCache adds a cache to the corresponding route.
-func (rb *RouteBuilder) WithRouteCache(cache cache.TTLCache, ageBounds httpcache.Age) *RouteBuilder {
-	rc, ee := httpcache.NewRouteCache(cache, ageBounds)
-
-	rb.routeCache = rc
-	rb.errors = append(rb.errors, ee...)
-	return rb
-}
-
-func (rb *RouteBuilder) setMethod(method string) *RouteBuilder {
-	if rb.method != "" {
-		rb.errors = append(rb.errors, errors.New("method already set"))
-	}
-	rb.method = method
-	return rb
-}
-
-// MethodGet HTTP method.
-func (rb *RouteBuilder) MethodGet() *RouteBuilder {
-	return rb.setMethod(http.MethodGet)
-}
-
-// MethodHead HTTP method.
-func (rb *RouteBuilder) MethodHead() *RouteBuilder {
-	return rb.setMethod(http.MethodHead)
-}
-
-// MethodPost HTTP method.
-func (rb *RouteBuilder) MethodPost() *RouteBuilder {
-	return rb.setMethod(http.MethodPost)
-}
-
-// MethodPut HTTP method.
-func (rb *RouteBuilder) MethodPut() *RouteBuilder {
-	return rb.setMethod(http.MethodPut)
-}
-
-// MethodPatch HTTP method.
-func (rb *RouteBuilder) MethodPatch() *RouteBuilder {
-	return rb.setMethod(http.MethodPatch)
-}
-
-// MethodDelete HTTP method.
-func (rb *RouteBuilder) MethodDelete() *RouteBuilder {
-	return rb.setMethod(http.MethodDelete)
-}
-
-// MethodConnect HTTP method.
-func (rb *RouteBuilder) MethodConnect() *RouteBuilder {
-	return rb.setMethod(http.MethodConnect)
-}
-
-// MethodOptions HTTP method.
-func (rb *RouteBuilder) MethodOptions() *RouteBuilder {
-	return rb.setMethod(http.MethodOptions)
+func (r Route) Middlewares() []patronhttp.Func {
+	return r.middlewares
 }
 
-// MethodTrace HTTP method.
-func (rb *RouteBuilder) MethodTrace() *RouteBuilder {
-	return rb.setMethod(http.MethodTrace)
+func (r Route) String() string {
+	return r.method + " " + r.path
 }
 
-// Build a route.
-func (rb *RouteBuilder) Build() (Route, error) {
-	// parse a list of HTTP numeric status codes that must be logged
-	cfg, _ := os.LookupEnv("PATRON_HTTP_STATUS_ERROR_LOGGING")
-	statusCodeLogger, err := middleware.NewStatusCodeLoggerHandler(cfg)
-	if err != nil {
-		return Route{}, fmt.Errorf("failed to parse status codes %q: %w", cfg, err)
-	}
-
-	if len(rb.errors) > 0 {
-		return Route{}, errs.Aggregate(rb.errors...)
-	}
-
-	if rb.method == "" {
-		return Route{}, errors.New("method is missing")
+// NewRoute creates a new raw route with functional configuration.
+func NewRoute(method, path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	if method == "" {
+		return nil, errors.New("method is empty")
 	}
 
-	var middlewares []middleware.Func
-	if rb.jaegerTrace {
-		// uses Jaeger/OpenTracing and Patron's response logging
-		loggingTracingMiddleware, err := middleware.NewLoggingTracing(rb.path, statusCodeLogger)
-		if err != nil {
-			return Route{}, err
-		}
-		middlewares = append(middlewares, loggingTracingMiddleware)
-	}
-
-	// uses a custom Patron metric for HTTP responses (with complete status code)
-	// it does not use Jaeger/OpenTracing
-	requestObserverMiddleware, err := middleware.NewRequestObserver(rb.method, rb.path)
-	if err != nil {
-		return Route{}, err
-	}
-	middlewares = append(middlewares, requestObserverMiddleware)
-
-	if rb.rateLimiter != nil {
-		rateLimiterMiddleware, err := middleware.NewRateLimiting(rb.rateLimiter)
-		if err != nil {
-			return Route{}, err
-		}
-		middlewares = append(middlewares, rateLimiterMiddleware)
-	}
-	if rb.authenticator != nil {
-		middlewares = append(middlewares, middleware.NewAuth(rb.authenticator))
-	}
-	if len(rb.middlewares) > 0 {
-		middlewares = append(middlewares, rb.middlewares...)
-	}
-	// cache middleware is always last, so that it caches only the headers of the handler
-	if rb.routeCache != nil {
-		if rb.method != http.MethodGet {
-			return Route{}, errors.New("cannot apply cache to a route with any method other than GET ")
-		}
-		cachingMiddleware, err := middleware.NewCaching(rb.routeCache)
-		if err != nil {
-			return Route{}, err
-		}
-		middlewares = append(middlewares, cachingMiddleware)
-	}
-
-	return Route{
-		path:        rb.path,
-		method:      rb.method,
-		handler:     rb.handler,
-		middlewares: middlewares,
-	}, nil
-}
-
-// NewFileServer constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewFileServer(path string, assetsDir string, fallbackPath string) *RouteBuilder {
-	var ee []error
-
 	if path == "" {
-		ee = append(ee, errors.New("path is empty"))
-	}
-
-	if assetsDir == "" {
-		ee = append(ee, errors.New("assets path is empty"))
-	} else {
-		_, err := os.Stat(assetsDir)
-		if os.IsNotExist(err) {
-			ee = append(ee, fmt.Errorf("assets directory [%s] doesn't exist", path))
-		} else if err != nil {
-			ee = append(ee, fmt.Errorf("error while checking assets dir: %w", err))
-		}
-	}
-
-	if fallbackPath == "" {
-		ee = append(ee, errors.New("fallback path is empty"))
-	} else {
-		_, err := os.Stat(fallbackPath)
-		if os.IsNotExist(err) {
-			ee = append(ee, fmt.Errorf("fallback file [%s] doesn't exist", fallbackPath))
-		} else if err != nil {
-			ee = append(ee, fmt.Errorf("error while checking fallback file: %w", err))
-		}
-	}
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		params := ExtractParams(r)
-
-		// get the absolute path to prevent directory traversal
-		path := fmt.Sprintf("%s%s", assetsDir, params["path"])
-
-		// check whether a file exists at the given path
-		info, err := os.Stat(path)
-		if os.IsNotExist(err) || info.IsDir() {
-			// file does not exist, serve index.html
-			http.ServeFile(w, r, fallbackPath)
-			return
-		} else if err != nil {
-			// if we got an error (that wasn't that the file doesn't exist) stating the
-			// file, return a 500 internal server error and stop
-			http.Error(w, "", http.StatusInternalServerError)
-			return
-		}
-
-		// otherwise, use server the specific file directly from the filesystem.
-		http.ServeFile(w, r, path)
-	}
-
-	return &RouteBuilder{path: path, errors: ee, handler: handler, method: http.MethodGet}
-}
-
-// NewRawRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewRawRouteBuilder(path string, handler http.HandlerFunc) *RouteBuilder {
-	var ee []error
-
-	if path == "" {
-		ee = append(ee, errors.New("path is empty"))
+		return nil, errors.New("path is empty")
 	}
 
 	if handler == nil {
-		ee = append(ee, errors.New("handler is nil"))
+		return nil, errors.New("handler is nil")
 	}
 
-	return &RouteBuilder{path: path, errors: ee, handler: handler}
-}
-
-// NewRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	var ee []error
-
-	if path == "" {
-		ee = append(ee, errors.New("path is empty"))
+	route := &Route{
+		method:  method,
+		path:    path,
+		handler: handler,
 	}
 
-	if processor == nil {
-		ee = append(ee, errors.New("processor is nil"))
+	for _, option := range oo {
+		err := option(route)
+		if err != nil {
+			return nil, err
+		}
 	}
 
-	return &RouteBuilder{path: path, errors: ee, handler: handler(processor)}
+	return route, nil
 }
 
-// NewGetRouteBuilder constructor
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewGetRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodGet()
+// NewGetRoute constructor.
+func NewGetRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodGet, path, handler, oo...)
 }
 
-// NewHeadRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewHeadRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodHead()
+// NewHeadRoute constructor.
+func NewHeadRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodHead, path, handler, oo...)
 }
 
-// NewPostRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewPostRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodPost()
+// NewPostRoute constructor.
+func NewPostRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodPost, path, handler, oo...)
 }
 
-// NewPutRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewPutRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodPut()
+// NewPutRoute constructor.
+func NewPutRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodPut, path, handler, oo...)
 }
 
-// NewPatchRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewPatchRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodPatch()
+// NewPatchRoute constructor.
+func NewPatchRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodPatch, path, handler, oo...)
 }
 
-// NewDeleteRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewDeleteRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodDelete()
+// NewDeleteRoute constructor.
+func NewDeleteRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodDelete, path, handler, oo...)
 }
 
-// NewConnectRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewConnectRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodConnect()
+// NewConnectRoute constructor.
+func NewConnectRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodConnect, path, handler, oo...)
 }
 
-// NewOptionsRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewOptionsRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodOptions()
+// NewOptionsRoute constructor.
+func NewOptionsRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodOptions, path, handler, oo...)
 }
 
-// NewTraceRouteBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewTraceRouteBuilder(path string, processor ProcessorFunc) *RouteBuilder {
-	return NewRouteBuilder(path, processor).MethodTrace()
+// NewTraceRoute constructor.
+func NewTraceRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
+	return NewRoute(http.MethodTrace, path, handler, oo...)
 }
 
-// RoutesBuilder creates a list of routes.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-type RoutesBuilder struct {
-	routes []Route
-	errors []error
+// Routes definition.
+type Routes struct {
+	routes []*Route
+	ee     []error
 }
 
-// Append a route to the list.
-func (rb *RoutesBuilder) Append(builder *RouteBuilder) *RoutesBuilder {
-	route, err := builder.Build()
+// Append route.
+func (r *Routes) Append(route *Route, err error) {
 	if err != nil {
-		rb.errors = append(rb.errors, err)
-	} else {
-		rb.routes = append(rb.routes, route)
-	}
-	return rb
-}
-
-// Build the routes.
-func (rb *RoutesBuilder) Build() ([]Route, error) {
-	duplicates := make(map[string]struct{}, len(rb.routes))
-
-	for _, r := range rb.routes {
-		key := strings.ToLower(r.method + "-" + r.path)
-		_, ok := duplicates[key]
-		if ok {
-			rb.errors = append(rb.errors, fmt.Errorf("route with key %s is duplicate", key))
-			continue
-		}
-		duplicates[key] = struct{}{}
+		r.ee = append(r.ee, err)
+		return
 	}
-
-	if len(rb.errors) > 0 {
-		return nil, errs.Aggregate(rb.errors...)
+	if route == nil {
+		r.ee = append(r.ee, errors.New("route is nil"))
+		return
 	}
-
-	return rb.routes, nil
+	r.routes = append(r.routes, route)
 }
 
-// NewRoutesBuilder constructor.
-//
-// Deprecated: Please use the new v2 package.
-// This package is frozen and no new functionality will be added.
-func NewRoutesBuilder() *RoutesBuilder {
-	return &RoutesBuilder{}
+// Result of the route aggregation.
+func (r *Routes) Result() ([]*Route, error) {
+	return r.routes, patronerrors.Aggregate(r.ee...)
 }
diff --git a/component/http/v2/route_option.go b/component/http/route_option.go
similarity index 99%
rename from component/http/v2/route_option.go
rename to component/http/route_option.go
index a62c5b725..16c30faad 100644
--- a/component/http/v2/route_option.go
+++ b/component/http/route_option.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"errors"
diff --git a/component/http/v2/router/httprouter/route.go b/component/http/router/httprouter/route.go
similarity index 91%
rename from component/http/v2/router/httprouter/route.go
rename to component/http/router/httprouter/route.go
index 0f5ee9c92..7912d9045 100644
--- a/component/http/v2/router/httprouter/route.go
+++ b/component/http/router/httprouter/route.go
@@ -6,12 +6,12 @@ import (
 	"net/http"
 	"os"
 
-	v2 "github.com/beatlabs/patron/component/http/v2"
+	patronhttp "github.com/beatlabs/patron/component/http"
 	"github.com/julienschmidt/httprouter"
 )
 
 // NewFileServerRoute returns a route that acts as a file server.
-func NewFileServerRoute(path string, assetsDir string, fallbackPath string) (*v2.Route, error) {
+func NewFileServerRoute(path string, assetsDir string, fallbackPath string) (*patronhttp.Route, error) {
 	if path == "" {
 		return nil, errors.New("path is empty")
 	}
@@ -67,5 +67,5 @@ func NewFileServerRoute(path string, assetsDir string, fallbackPath string) (*v2
 		http.ServeFile(w, r, path)
 	}
 
-	return v2.NewRoute(http.MethodGet, path, handler)
+	return patronhttp.NewRoute(http.MethodGet, path, handler)
 }
diff --git a/component/http/v2/router/httprouter/route_test.go b/component/http/router/httprouter/route_test.go
similarity index 100%
rename from component/http/v2/router/httprouter/route_test.go
rename to component/http/router/httprouter/route_test.go
diff --git a/component/http/v2/router/httprouter/router.go b/component/http/router/httprouter/router.go
similarity index 81%
rename from component/http/v2/router/httprouter/router.go
rename to component/http/router/httprouter/router.go
index d40f9425a..76ac2ba26 100644
--- a/component/http/v2/router/httprouter/router.go
+++ b/component/http/router/httprouter/router.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"os"
 
+	patronhttp "github.com/beatlabs/patron/component/http"
 	"github.com/beatlabs/patron/component/http/middleware"
-	v2 "github.com/beatlabs/patron/component/http/v2"
 	"github.com/julienschmidt/httprouter"
 	"golang.org/x/exp/slog"
 )
@@ -17,11 +17,11 @@ type OptionFunc func(*Config) error
 
 // Config definition.
 type Config struct {
-	aliveCheckFunc           v2.LivenessCheckFunc
-	readyCheckFunc           v2.ReadyCheckFunc
+	aliveCheckFunc           patronhttp.LivenessCheckFunc
+	readyCheckFunc           patronhttp.ReadyCheckFunc
 	deflateLevel             int
 	middlewares              []middleware.Func
-	routes                   []*v2.Route
+	routes                   []*patronhttp.Route
 	enableProfilingExpVar    bool
 	appNameVersionMiddleware middleware.Func
 }
@@ -29,8 +29,8 @@ type Config struct {
 // New creates an http router with functional options.
 func New(oo ...OptionFunc) (*httprouter.Router, error) {
 	cfg := &Config{
-		aliveCheckFunc: func() v2.AliveStatus { return v2.Alive },
-		readyCheckFunc: func() v2.ReadyStatus { return v2.Ready },
+		aliveCheckFunc: func() patronhttp.AliveStatus { return patronhttp.Alive },
+		readyCheckFunc: func() patronhttp.ReadyStatus { return patronhttp.Ready },
 		deflateLevel:   defaultDeflateLevel,
 	}
 
@@ -41,19 +41,19 @@ func New(oo ...OptionFunc) (*httprouter.Router, error) {
 		}
 	}
 
-	var stdRoutes []*v2.Route
+	var stdRoutes []*patronhttp.Route
 
 	mux := httprouter.New()
-	stdRoutes = append(stdRoutes, v2.MetricRoute())
-	stdRoutes = append(stdRoutes, v2.ProfilingRoutes(cfg.enableProfilingExpVar)...)
+	stdRoutes = append(stdRoutes, patronhttp.MetricRoute())
+	stdRoutes = append(stdRoutes, patronhttp.ProfilingRoutes(cfg.enableProfilingExpVar)...)
 
-	route, err := v2.LivenessCheckRoute(cfg.aliveCheckFunc)
+	route, err := patronhttp.LivenessCheckRoute(cfg.aliveCheckFunc)
 	if err != nil {
 		return nil, err
 	}
 	stdRoutes = append(stdRoutes, route)
 
-	route, err = v2.ReadyCheckRoute(cfg.readyCheckFunc)
+	route, err = patronhttp.ReadyCheckRoute(cfg.readyCheckFunc)
 	if err != nil {
 		return nil, err
 	}
diff --git a/component/http/v2/router/httprouter/router_option.go b/component/http/router/httprouter/router_option.go
similarity index 88%
rename from component/http/v2/router/httprouter/router_option.go
rename to component/http/router/httprouter/router_option.go
index 8a90b5030..30117f55e 100644
--- a/component/http/v2/router/httprouter/router_option.go
+++ b/component/http/router/httprouter/router_option.go
@@ -3,12 +3,12 @@ package httprouter
 import (
 	"errors"
 
+	patronhttp "github.com/beatlabs/patron/component/http"
 	"github.com/beatlabs/patron/component/http/middleware"
-	v2 "github.com/beatlabs/patron/component/http/v2"
 )
 
 // WithRoutes option for providing routes to the router.
-func WithRoutes(routes ...*v2.Route) OptionFunc {
+func WithRoutes(routes ...*patronhttp.Route) OptionFunc {
 	return func(cfg *Config) error {
 		if len(routes) == 0 {
 			return errors.New("routes are empty")
@@ -19,7 +19,7 @@ func WithRoutes(routes ...*v2.Route) OptionFunc {
 }
 
 // WithAliveCheck option for the router.
-func WithAliveCheck(acf v2.LivenessCheckFunc) OptionFunc {
+func WithAliveCheck(acf patronhttp.LivenessCheckFunc) OptionFunc {
 	return func(cfg *Config) error {
 		if acf == nil {
 			return errors.New("alive check function is nil")
@@ -30,7 +30,7 @@ func WithAliveCheck(acf v2.LivenessCheckFunc) OptionFunc {
 }
 
 // WithReadyCheck option for the router.
-func WithReadyCheck(rcf v2.ReadyCheckFunc) OptionFunc {
+func WithReadyCheck(rcf patronhttp.ReadyCheckFunc) OptionFunc {
 	return func(cfg *Config) error {
 		if rcf == nil {
 			return errors.New("ready check function is nil")
diff --git a/component/http/v2/router/httprouter/testdata/index.html b/component/http/router/httprouter/testdata/index.html
similarity index 100%
rename from component/http/v2/router/httprouter/testdata/index.html
rename to component/http/router/httprouter/testdata/index.html
diff --git a/component/http/v2/component.go b/component/http/v2/component.go
deleted file mode 100644
index bc1339d56..000000000
--- a/component/http/v2/component.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Package v2 provides a ready to use HTTP component.
-package v2
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"net/http"
-	"os"
-	"strconv"
-	"sync"
-	"time"
-
-	"golang.org/x/exp/slog"
-)
-
-const (
-	defaultPort                = 50000
-	defaultReadTimeout         = 30 * time.Second
-	defaultWriteTimeout        = 60 * time.Second
-	defaultIdleTimeout         = 240 * time.Second
-	defaultHandlerTimeout      = 59 * time.Second // should be smaller than write timeout
-	defaultShutdownGracePeriod = 5 * time.Second
-)
-
-func port() (int, error) {
-	port, ok := os.LookupEnv("PATRON_HTTP_DEFAULT_PORT")
-	if !ok {
-		slog.Debug("using default port", slog.Int("port", defaultPort))
-		return defaultPort, nil
-	}
-	portVal, err := strconv.ParseInt(port, 10, 32)
-	if err != nil {
-		return 0, fmt.Errorf("env var for HTTP default port is not valid: %w", err)
-	}
-	slog.Debug("using port", slog.Int64("port", portVal))
-	return int(portVal), nil
-}
-
-func readTimeout() (time.Duration, error) {
-	httpTimeout, ok := os.LookupEnv("PATRON_HTTP_READ_TIMEOUT")
-	if !ok {
-		slog.Debug("using default read timeout", slog.Duration("timeout", defaultReadTimeout))
-		return defaultReadTimeout, nil
-	}
-	timeout, err := time.ParseDuration(httpTimeout)
-	if err != nil {
-		return 0, fmt.Errorf("env var for HTTP read timeout is not valid: %w", err)
-	}
-	slog.Debug("using read timeout", slog.Duration("timeout", timeout))
-	return timeout, nil
-}
-
-func writeTimeout() (time.Duration, error) {
-	httpTimeout, ok := os.LookupEnv("PATRON_HTTP_WRITE_TIMEOUT")
-	if !ok {
-		slog.Debug("using default write timeout", slog.Duration("timeout", defaultWriteTimeout))
-		return defaultWriteTimeout, nil
-	}
-	timeout, err := time.ParseDuration(httpTimeout)
-	if err != nil {
-		return 0, fmt.Errorf("env var for HTTP write timeout is not valid: %w", err)
-	}
-	slog.Debug("using write timeout", slog.Duration("timeout", timeout))
-	return timeout, nil
-}
-
-// Component implementation of an HTTP router.
-type Component struct {
-	port                int
-	readTimeout         time.Duration
-	writeTimeout        time.Duration
-	shutdownGracePeriod time.Duration
-	handlerTimeout      time.Duration
-	handler             http.Handler
-	mu                  sync.Mutex
-	certFile            string
-	keyFile             string
-}
-
-// New creates an HTTP component configurable by functional options.
-func New(handler http.Handler, oo ...OptionFunc) (*Component, error) {
-	if handler == nil {
-		return nil, errors.New("handler is nil")
-	}
-
-	port, err := port()
-	if err != nil {
-		return nil, err
-	}
-
-	readTimeout, err := readTimeout()
-	if err != nil {
-		return nil, err
-	}
-
-	writeTimeout, err := writeTimeout()
-	if err != nil {
-		return nil, err
-	}
-
-	cmp := &Component{
-		port:                port,
-		readTimeout:         readTimeout,
-		writeTimeout:        writeTimeout,
-		shutdownGracePeriod: defaultShutdownGracePeriod,
-		handlerTimeout:      defaultHandlerTimeout,
-		handler:             handler,
-	}
-
-	for _, option := range oo {
-		err := option(cmp)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return cmp, nil
-}
-
-// Run starts the HTTP server and returns only if listening and/or serving failed, or if the context was canceled.
-func (c *Component) Run(ctx context.Context) error {
-	c.mu.Lock()
-	chFail := make(chan error)
-	srv := c.createHTTPServer()
-	go c.listenAndServe(srv, chFail)
-	c.mu.Unlock()
-
-	select {
-	case <-ctx.Done():
-		slog.Info("shutting down HTTP component")
-		ctx, cancel := context.WithTimeout(context.Background(), c.shutdownGracePeriod)
-		defer cancel()
-		return srv.Shutdown(ctx)
-	case err := <-chFail:
-		return err
-	}
-}
-
-func (c *Component) createHTTPServer() *http.Server {
-	return &http.Server{
-		Addr:         fmt.Sprintf(":%d", c.port),
-		ReadTimeout:  c.readTimeout,
-		WriteTimeout: c.writeTimeout,
-		IdleTimeout:  defaultIdleTimeout,
-		Handler:      http.TimeoutHandler(c.handler, c.handlerTimeout, ""),
-	}
-}
-
-func (c *Component) listenAndServe(srv *http.Server, ch chan<- error) {
-	if c.certFile != "" && c.keyFile != "" {
-		slog.Debug("HTTPS component listening", slog.Int("port", c.port))
-		ch <- srv.ListenAndServeTLS(c.certFile, c.keyFile)
-		return
-	}
-
-	slog.Debug("HTTP component listening", slog.Int("port", c.port))
-	ch <- srv.ListenAndServe()
-}
diff --git a/component/http/v2/encoding/json/json.go b/component/http/v2/encoding/json/json.go
deleted file mode 100644
index ea34dbcc1..000000000
--- a/component/http/v2/encoding/json/json.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Package json contains helper methods to handler requests and responses more easily.
-package json
-
-import (
-	"fmt"
-	"net/http"
-	"strings"
-
-	"github.com/beatlabs/patron/encoding"
-	"github.com/beatlabs/patron/encoding/json"
-)
-
-const (
-	singleStar = "*"
-	doubleStar = "*/*"
-)
-
-// ReadRequest validates the request headers and decodes into the provided payload.
-func ReadRequest(req *http.Request, payload interface{}) error {
-	err := validateContentTypeHeader(req)
-	if err != nil {
-		return err
-	}
-
-	return json.Decode(req.Body, payload)
-}
-
-func validateContentTypeHeader(req *http.Request) error {
-	header, ok := req.Header[encoding.ContentTypeHeader]
-	if !ok {
-		return nil
-	}
-
-	if len(header) == 0 {
-		return nil
-	}
-
-	switch header[0] {
-	case singleStar, doubleStar, json.Type, json.TypeCharset:
-		return nil
-	default:
-		return fmt.Errorf("invalid content type provided: %s", header[0])
-	}
-}
-
-// WriteResponse validates the request headers and encodes the provided payload into the response.
-func WriteResponse(w http.ResponseWriter, status int, payload interface{}) error {
-	buf, err := json.Encode(payload)
-	if err != nil {
-		return err
-	}
-
-	w.WriteHeader(status)
-	w.Header().Add(encoding.ContentTypeHeader, json.Type)
-
-	_, err = w.Write(buf)
-	if err != nil {
-		return fmt.Errorf("failed to write to response: %w", err)
-	}
-
-	return nil
-}
-
-// ValidateAcceptHeader checks that the accept header instruction allows for JSON handling.
-func ValidateAcceptHeader(req *http.Request) error {
-	header, ok := req.Header[encoding.AcceptHeader]
-	if !ok {
-		return nil
-	}
-
-	if len(header) == 0 {
-		return nil
-	}
-
-	if header[0] == "" {
-		return nil
-	}
-
-	jsonHeaderFound := false
-
-	ah := getMultiValueHeaders(header[0])
-	for _, v := range ah {
-		if isValidAcceptHeader(v) {
-			jsonHeaderFound = true
-			break
-		}
-	}
-
-	if !jsonHeaderFound {
-		return fmt.Errorf("invalid accept header: %s", header[0])
-	}
-
-	return nil
-}
-
-func isValidAcceptHeader(header string) bool {
-	parts := strings.SplitN(header, ";", 2)
-	switch parts[0] {
-	case singleStar, doubleStar, "identity", json.Type, json.TypeCharset:
-		return true
-	default:
-		return false
-	}
-}
-
-func getMultiValueHeaders(header string) []string {
-	if !strings.Contains(header, ",") {
-		return []string{header}
-	}
-
-	splitHeaders := strings.Split(header, ",")
-
-	trimmedHeaders := make([]string, 0, len(splitHeaders))
-	for _, v := range splitHeaders {
-		trimmedHeaders = append(trimmedHeaders, strings.TrimSpace(v))
-	}
-
-	return trimmedHeaders
-}
diff --git a/component/http/v2/route.go b/component/http/v2/route.go
deleted file mode 100644
index 4a721d6c6..000000000
--- a/component/http/v2/route.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package v2
-
-import (
-	"errors"
-	"net/http"
-
-	patronhttp "github.com/beatlabs/patron/component/http/middleware"
-	patronerrors "github.com/beatlabs/patron/errors"
-)
-
-// RouteOptionFunc definition for configuring the route in a functional way.
-type RouteOptionFunc func(route *Route) error
-
-// Route definition of an HTTP route.
-type Route struct {
-	method      string
-	path        string
-	handler     http.HandlerFunc
-	middlewares []patronhttp.Func
-}
-
-func (r Route) Method() string {
-	return r.method
-}
-
-func (r Route) Path() string {
-	return r.path
-}
-
-func (r Route) Handler() http.HandlerFunc {
-	return r.handler
-}
-
-func (r Route) Middlewares() []patronhttp.Func {
-	return r.middlewares
-}
-
-func (r Route) String() string {
-	return r.method + " " + r.path
-}
-
-// NewRoute creates a new raw route with functional configuration.
-func NewRoute(method, path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	if method == "" {
-		return nil, errors.New("method is empty")
-	}
-
-	if path == "" {
-		return nil, errors.New("path is empty")
-	}
-
-	if handler == nil {
-		return nil, errors.New("handler is nil")
-	}
-
-	route := &Route{
-		method:  method,
-		path:    path,
-		handler: handler,
-	}
-
-	for _, option := range oo {
-		err := option(route)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return route, nil
-}
-
-// NewGetRoute constructor.
-func NewGetRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodGet, path, handler, oo...)
-}
-
-// NewHeadRoute constructor.
-func NewHeadRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodHead, path, handler, oo...)
-}
-
-// NewPostRoute constructor.
-func NewPostRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodPost, path, handler, oo...)
-}
-
-// NewPutRoute constructor.
-func NewPutRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodPut, path, handler, oo...)
-}
-
-// NewPatchRoute constructor.
-func NewPatchRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodPatch, path, handler, oo...)
-}
-
-// NewDeleteRoute constructor.
-func NewDeleteRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodDelete, path, handler, oo...)
-}
-
-// NewConnectRoute constructor.
-func NewConnectRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodConnect, path, handler, oo...)
-}
-
-// NewOptionsRoute constructor.
-func NewOptionsRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodOptions, path, handler, oo...)
-}
-
-// NewTraceRoute constructor.
-func NewTraceRoute(path string, handler http.HandlerFunc, oo ...RouteOptionFunc) (*Route, error) {
-	return NewRoute(http.MethodTrace, path, handler, oo...)
-}
-
-// Routes definition.
-type Routes struct {
-	routes []*Route
-	ee     []error
-}
-
-// Append route.
-func (r *Routes) Append(route *Route, err error) {
-	if err != nil {
-		r.ee = append(r.ee, err)
-		return
-	}
-	if route == nil {
-		r.ee = append(r.ee, errors.New("route is nil"))
-		return
-	}
-	r.routes = append(r.routes, route)
-}
-
-// Result of the route aggregation.
-func (r *Routes) Result() ([]*Route, error) {
-	return r.routes, patronerrors.Aggregate(r.ee...)
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index 9c6f5bb4e..d03470d34 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -57,16 +57,12 @@ services:
     volumes:
       - 'redis_data:/bitnami/redis/data'
   localstack:
-    image: localstack/localstack:0.14
-    network_mode: bridge
+    image: localstack/localstack:2.1
     ports:
-      - "127.0.0.1:4510-4559:4510-4559" # external service port range
-      - "127.0.0.1:4566:4566" # LocalStack Edge Proxy
+      - "127.0.0.1:4566:4566" # LocalStack Gateway
+      - "127.0.0.1:4510-4559:4510-4559" # external services port range
     environment:
-      - SERVICES=sns,sqs
-      - DEBUG=1
-      - DATA_DIR=${DATA_DIR-}
-      - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack
+      - DEBUG=${DEBUG-}
       - DOCKER_HOST=unix:///var/run/docker.sock
       - AWS_ACCESS_KEY_ID=test
       - AWS_SECRET_ACCESS_KEY=test
diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml
index 91d3171f2..04ac6ca53 100644
--- a/examples/docker-compose.yml
+++ b/examples/docker-compose.yml
@@ -2,51 +2,52 @@ version: '2.1'
 
 services:
   rabbitmq:
-    image: rabbitmq:3-management
-    hostname: "rabbitmq"
-    expose:
-      - "15672"
-      - "5672"
+    image: docker.io/bitnami/rabbitmq:3.9
     ports:
-      - "15672:15672"
-      - "5672:5672"
+      - '4369:4369'
+      - '5551:5551'
+      - '5552:5552'
+      - '5672:5672'
+      - '25672:25672'
+      - '15672:15672'
+    environment:
+      - RABBITMQ_SECURE_PASSWORD=yes
+    volumes:
+      - 'rabbitmq_data:/bitnami/rabbitmq/mnesia'
   zookeeper:
-    image: confluentinc/cp-zookeeper:6.0.0
-    hostname: "zookeeper"
-    expose:
-      - "2181"
+    image: 'bitnami/zookeeper:latest'
     ports:
-      - "2181:2181"
+      - '2181:2181'
     environment:
-      ZOOKEEPER_CLIENT_PORT: 2181
-      ZOOKEEPER_TICK_TIME: 2000
+      - ALLOW_ANONYMOUS_LOGIN=yes
   kafka:
-    image: confluentinc/cp-kafka:6.0.0
-    hostname: "kafka"
-    expose:
-      - "9092"
-      - "29092"
+    image: 'bitnami/kafka:2'
     ports:
-      - "9092:9092"
-      - "29092:29092"
+      - '9092:9092'
+      - '9093:9093'
+    environment:
+      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
+      - ALLOW_PLAINTEXT_LISTENER=yes
+      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CLIENT:PLAINTEXT,EXTERNAL:PLAINTEXT
+      - KAFKA_CFG_LISTENERS=CLIENT://:9092,EXTERNAL://:9093
+      - KAFKA_CFG_ADVERTISED_LISTENERS=CLIENT://kafka:9092,EXTERNAL://localhost:9093
+      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=CLIENT
     depends_on:
       - zookeeper
-    environment:
-      KAFKA_BROKER_ID: 1
-      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
-      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
-      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
   localstack:
-    image: localstack/localstack:0.12.0
-    hostname: localstack
+    image: localstack/localstack:2.1
     ports:
-      - "4566:4566"
+      - "127.0.0.1:4566:4566" # LocalStack Gateway
+      - "127.0.0.1:4510-4559:4510-4559" # external services port range
     environment:
-      - LOCALSTACK_SERVICES=sqs,sns
-      - LOCALSTACK_DEBUG=1
-      - LOCALSTACK_DATA_DIR=/tmp/localstack/data
+      - DEBUG=${DEBUG-}
+      - DOCKER_HOST=unix:///var/run/docker.sock
       - AWS_ACCESS_KEY_ID=test
       - AWS_SECRET_ACCESS_KEY=test
       - AWS_DEFAULT_REGION=eu-west-1
     volumes:
-      - '/var/run/docker.sock:/var/run/docker.sock'
+      - "${TMPDIR:-/tmp}/localstack:/tmp/localstack"
+      - "/var/run/docker.sock:/var/run/docker.sock"
+volumes:
+  rabbitmq_data:
+    driver: local
diff --git a/examples/examples.go b/examples/examples.go
index 1f5e02fdd..30a4ef17b 100644
--- a/examples/examples.go
+++ b/examples/examples.go
@@ -17,7 +17,7 @@ const (
 	GRPCPort   = "50002"
 	GRPCTarget = "localhost:50002"
 
-	AMQPURL          = "amqp://guest:guest@localhost:5672/"
+	AMQPURL          = "amqp://user:bitnami@localhost:5672/"
 	AMQPQueue        = "patron"
 	AMQPExchangeName = "patron"
 	AMQPExchangeType = amqp.ExchangeFanout
@@ -31,7 +31,7 @@ const (
 
 	KafkaTopic  = "patron-topic"
 	KafkaGroup  = "patron-group"
-	KafkaBroker = "localhost:9092"
+	KafkaBroker = "localhost:9093"
 )
 
 func CreateSQSAPI() (*sqs.Client, error) {
diff --git a/examples/service/http.go b/examples/service/http.go
index 5d1307096..b694135ad 100644
--- a/examples/service/http.go
+++ b/examples/service/http.go
@@ -6,8 +6,8 @@ import (
 	"net/http"
 
 	"github.com/beatlabs/patron"
-	v2 "github.com/beatlabs/patron/component/http/v2"
-	"github.com/beatlabs/patron/component/http/v2/router/httprouter"
+	patronhttp "github.com/beatlabs/patron/component/http"
+	"github.com/beatlabs/patron/component/http/router/httprouter"
 	"github.com/beatlabs/patron/log"
 )
 
@@ -25,8 +25,8 @@ func createHttpRouter() (patron.Component, error) {
 		rw.WriteHeader(http.StatusOK)
 	}
 
-	var routes v2.Routes
-	routes.Append(v2.NewGetRoute("/", handler))
+	var routes patronhttp.Routes
+	routes.Append(patronhttp.NewGetRoute("/", handler))
 	rr, err := routes.Result()
 	if err != nil {
 		return nil, fmt.Errorf("failed to create routes: %w", err)
@@ -37,5 +37,5 @@ func createHttpRouter() (patron.Component, error) {
 		return nil, fmt.Errorf("failed to create http router: %w", err)
 	}
 
-	return v2.New(router)
+	return patronhttp.New(router)
 }
diff --git a/go.mod b/go.mod
index 097f7cb0a..2b19fafa7 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/beatlabs/patron
 
-go 1.19
+go 1.20
 
 require (
 	github.com/Shopify/sarama v1.38.1


Test patch:
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 423f45a8e..3f36f4c57 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -6,20 +6,21 @@ on:
   pull_request:
 jobs:
   build:
-    name: CI
-    runs-on: ubuntu-20.04
+    name: E2E
+    runs-on: ubuntu-latest
     steps:
+      - name: Check out source code
+        uses: actions/checkout@v3
+
       - name: Set up Go
         uses: actions/setup-go@v4
         with:
-          go-version: 1.19
-
-      - name: Check out source code
-        uses: actions/checkout@v3
+          go-version-file: 'go.mod'      
 
       - name: Starting dependencies
         run:  |
           docker-compose up -d
+          sleep 10
         working-directory: ./examples/
 
       - name: Starting service
diff --git a/component/http/alive_test.go b/component/http/alive_test.go
deleted file mode 100644
index 27b48f04e..000000000
--- a/component/http/alive_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package http
-
-import (
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_aliveCheckRoute(t *testing.T) {
-	tests := []struct {
-		name string
-		acf  AliveCheckFunc
-		want int
-	}{
-		{"alive", func() AliveStatus { return Alive }, http.StatusOK},
-		{"unresponsive", func() AliveStatus { return Unresponsive }, http.StatusServiceUnavailable},
-		{"default", func() AliveStatus { return 10 }, http.StatusOK},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			r, err := aliveCheckRoute(tt.acf).Build()
-			assert.NoError(t, err)
-			resp := httptest.NewRecorder()
-			req, err := http.NewRequest("GET", "/alive", nil)
-			assert.NoError(t, err)
-			r.handler(resp, req)
-			assert.Equal(t, tt.want, resp.Code)
-		})
-	}
-}
diff --git a/component/http/v2/check_test.go b/component/http/check_test.go
similarity index 99%
rename from component/http/v2/check_test.go
rename to component/http/check_test.go
index 4c32fda01..4057a07bd 100644
--- a/component/http/v2/check_test.go
+++ b/component/http/check_test.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"net/http"
diff --git a/component/http/v2/component_option_test.go b/component/http/component_option_test.go
similarity index 99%
rename from component/http/v2/component_option_test.go
rename to component/http/component_option_test.go
index 67f9d72c1..3f9ac7ced 100644
--- a/component/http/v2/component_option_test.go
+++ b/component/http/component_option_test.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"testing"
diff --git a/component/http/component_test.go b/component/http/component_test.go
index 461771345..4c0ca8f42 100644
--- a/component/http/component_test.go
+++ b/component/http/component_test.go
@@ -2,180 +2,139 @@ package http
 
 import (
 	"context"
-	"errors"
+	"fmt"
+	"net"
 	"net/http"
 	"testing"
 	"time"
 
-	"github.com/beatlabs/patron/component/http/middleware"
-	errs "github.com/beatlabs/patron/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
-func TestBuilderWithoutOptions(t *testing.T) {
-	got, err := NewBuilder().Create()
-	assert.NotNil(t, got)
-	assert.NoError(t, err)
-}
-
-func TestComponent_ListenAndServe_DefaultRoutes_Shutdown(t *testing.T) {
-	rb := NewRoutesBuilder().
-		Append(NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodGet().WithTrace())
-	s, err := NewBuilder().WithRoutesBuilder(rb).WithPort(50013).Create()
-	assert.NoError(t, err)
-	done := make(chan bool)
-	ctx, cnl := context.WithCancel(context.Background())
-	go func() {
-		assert.NoError(t, s.Run(ctx))
-		done <- true
-	}()
-	time.Sleep(100 * time.Millisecond)
-	assert.Len(t, s.routes, 15)
-	cnl()
-	assert.True(t, <-done)
-}
-
-func TestComponent_ListenAndServeTLS_DefaultRoutes_Shutdown(t *testing.T) {
-	rb := NewRoutesBuilder().Append(NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodGet())
-	s, err := NewBuilder().WithRoutesBuilder(rb).WithSSL("testdata/server.pem", "testdata/server.key").WithPort(50016).Create()
-	assert.NoError(t, err)
-	done := make(chan bool)
-	ctx, cnl := context.WithCancel(context.Background())
-	go func() {
-		assert.NoError(t, s.Run(ctx))
-		done <- true
-	}()
-	time.Sleep(100 * time.Millisecond)
-	assert.Len(t, s.routes, 15)
-	cnl()
-	assert.True(t, <-done)
-}
+type stubHandler struct{}
 
-func TestComponent_ListenAndServeTLS_FailsInvalidCerts(t *testing.T) {
-	rb := NewRoutesBuilder().Append(NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodGet())
-	s, err := NewBuilder().WithRoutesBuilder(rb).WithSSL("testdata/server.pem", "testdata/server.pem").Create()
-	assert.NoError(t, err)
-	assert.Error(t, s.Run(context.Background()))
-}
-
-func Test_createHTTPServer(t *testing.T) {
-	cmp := Component{
-		httpPort:         10000,
-		httpReadTimeout:  5 * time.Second,
-		httpWriteTimeout: 10 * time.Second,
-	}
-	s := cmp.createHTTPServer()
-	assert.NotNil(t, s)
-	assert.Equal(t, ":10000", s.Addr)
-	assert.Equal(t, 5*time.Second, s.ReadTimeout)
-	assert.Equal(t, 10*time.Second, s.WriteTimeout)
+func (s stubHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) {
+	rw.WriteHeader(200)
 }
 
-func TestBuilder_WithShutdownGracePeriod(t *testing.T) {
-	t.Parallel()
-	testCases := map[string]struct {
-		gp     time.Duration
-		expErr string
-	}{
-		"success":     {gp: 10 * time.Second},
-		"wrong value": {gp: -10 * time.Second, expErr: "negative or zero shutdown grace period provided\n"},
-	}
-
-	for name, tt := range testCases {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			cc, err := NewBuilder().WithShutdownGracePeriod(tt.gp).Create()
-			if tt.expErr != "" {
-				assert.EqualError(t, err, tt.expErr)
-				assert.Nil(t, cc)
-			} else {
-				assert.NoError(t, err)
-				require.NotNil(t, cc)
-				assert.Equal(t, tt.gp, cc.shutdownGracePeriod)
-			}
-		})
+func TestNew(t *testing.T) {
+	hnd := &stubHandler{}
+	type args struct {
+		handler      http.Handler
+		oo           []OptionFunc
+		port         string
+		readTimeout  string
+		writeTimeout string
 	}
-}
-
-func Test_createHTTPServerUsingBuilder(t *testing.T) {
-	var httpBuilderNoErrors []error
-	httpBuilderAllErrors := []error{
-		errors.New("nil AliveCheckFunc was provided"),
-		errors.New("nil ReadyCheckFunc provided"),
-		errors.New("invalid HTTP Port provided"),
-		errors.New("negative or zero read timeout provided"),
-		errors.New("negative or zero write timeout provided"),
-		errors.New("provided deflate level value not in the [-2, 9] range"),
-		errors.New("route builder is nil"),
-		errors.New("empty list of middlewares provided"),
-		errors.New("invalid cert or key provided"),
-		errors.New("negative or zero shutdown grace period provided"),
-	}
-
-	rb := NewRoutesBuilder().Append(NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodGet())
-
 	tests := map[string]struct {
-		acf      AliveCheckFunc
-		rcf      ReadyCheckFunc
-		p        int
-		rt       time.Duration
-		wt       time.Duration
-		gp       time.Duration
-		dl       int
-		rb       *RoutesBuilder
-		mm       []middleware.Func
-		c        string
-		k        string
-		wantErrs []error
+		args        args
+		expected    *Component
+		expectedErr string
 	}{
 		"success": {
-			acf: DefaultAliveCheck,
-			rcf: DefaultReadyCheck,
-			p:   defaultPort,
-			rt:  defaultReadTimeout,
-			wt:  defaultIdleTimeout,
-			dl:  defaultDeflateLevel,
-			gp:  defaultShutdownGracePeriod,
-			rb:  rb,
-			mm: []middleware.Func{
-				middleware.NewRecovery(),
+			args: args{handler: hnd},
+			expected: &Component{
+				port:                defaultPort,
+				readTimeout:         defaultReadTimeout,
+				writeTimeout:        defaultWriteTimeout,
+				shutdownGracePeriod: defaultShutdownGracePeriod,
+				handlerTimeout:      defaultHandlerTimeout,
+				handler:             hnd,
+			},
+		},
+		"success, env vars": {
+			args: args{
+				handler:      hnd,
+				port:         "8080",
+				readTimeout:  "10s",
+				writeTimeout: "11s",
+			},
+			expected: &Component{
+				port:                8080,
+				readTimeout:         10 * time.Second,
+				writeTimeout:        11 * time.Second,
+				shutdownGracePeriod: defaultShutdownGracePeriod,
+				handlerTimeout:      defaultHandlerTimeout,
+				handler:             hnd,
+			},
+		},
+		"failure, port env vars": {
+			args: args{
+				handler: hnd,
+				port:    "aaa",
+			},
+			expectedErr: `env var for HTTP default port is not valid: strconv.ParseInt: parsing "aaa": invalid syntax`,
+		},
+		"failure, read timeout env vars": {
+			args: args{
+				handler:     hnd,
+				readTimeout: "aaa",
+			},
+			expectedErr: `env var for HTTP read timeout is not valid: time: invalid duration "aaa"`,
+		},
+		"failure, write timeout env vars": {
+			args: args{
+				handler:      hnd,
+				writeTimeout: "aaa",
 			},
-			c:        "cert.file",
-			k:        "key.file",
-			wantErrs: httpBuilderNoErrors,
+			expectedErr: `env var for HTTP write timeout is not valid: time: invalid duration "aaa"`,
 		},
-		"error in all builder steps": {
-			acf:      nil,
-			rcf:      nil,
-			p:        -1,
-			rt:       -10 * time.Second,
-			wt:       -20 * time.Second,
-			gp:       -15 * time.Second,
-			dl:       -8,
-			rb:       nil,
-			mm:       []middleware.Func{},
-			c:        "",
-			k:        "",
-			wantErrs: httpBuilderAllErrors,
+		"missing handler": {
+			args:        args{handler: nil},
+			expectedErr: "handler is nil",
+		},
+		"option error": {
+			args:        args{handler: &stubHandler{}, oo: []OptionFunc{WithPort(500000)}},
+			expectedErr: "invalid HTTP Port provided",
 		},
 	}
-
 	for name, tt := range tests {
 		tt := tt
 		t.Run(name, func(t *testing.T) {
-			gotHTTPComponent, err := NewBuilder().WithAliveCheckFunc(tt.acf).WithReadyCheckFunc(tt.rcf).
-				WithPort(tt.p).WithReadTimeout(tt.rt).WithWriteTimeout(tt.wt).WithDeflateLevel(tt.dl).WithRoutesBuilder(tt.rb).
-				WithMiddlewares(tt.mm...).WithSSL(tt.c, tt.k).WithShutdownGracePeriod(tt.gp).Create()
+			if tt.args.port != "" {
+				t.Setenv("PATRON_HTTP_DEFAULT_PORT", tt.args.port)
+			}
 
-			if len(tt.wantErrs) > 0 {
-				assert.EqualError(t, err, errs.Aggregate(tt.wantErrs...).Error())
-				assert.Nil(t, gotHTTPComponent)
+			if tt.args.readTimeout != "" {
+				t.Setenv("PATRON_HTTP_READ_TIMEOUT", tt.args.readTimeout)
+			}
+
+			if tt.args.writeTimeout != "" {
+				t.Setenv("PATRON_HTTP_WRITE_TIMEOUT", tt.args.writeTimeout)
+			}
+
+			got, err := New(tt.args.handler, tt.args.oo...)
+			if tt.expectedErr != "" {
+				assert.EqualError(t, err, tt.expectedErr)
+				assert.Nil(t, got)
 			} else {
-				assert.NotNil(t, gotHTTPComponent)
-				assert.IsType(t, &Component{}, gotHTTPComponent)
+				assert.NoError(t, err)
+				assert.Equal(t, tt.expected, got)
 			}
 		})
 	}
 }
+
+func TestComponent_ListenAndServe_DefaultRoutes_Shutdown(t *testing.T) {
+	listener, err := net.Listen("tcp", ":0") //nolint:gosec
+	require.NoError(t, err)
+	port, ok := listener.Addr().(*net.TCPAddr)
+	assert.True(t, ok)
+	require.NoError(t, listener.Close())
+
+	cmp, err := New(&stubHandler{}, WithPort(port.Port))
+	assert.NoError(t, err)
+	done := make(chan bool)
+	ctx, cnl := context.WithCancel(context.Background())
+	go func() {
+		assert.NoError(t, cmp.Run(ctx))
+		done <- true
+	}()
+	time.Sleep(10 * time.Millisecond)
+	rsp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port.Port))
+	assert.NoError(t, err)
+	assert.Equal(t, http.StatusOK, rsp.StatusCode)
+	cnl()
+	assert.True(t, <-done)
+}
diff --git a/component/http/errors_test.go b/component/http/errors_test.go
deleted file mode 100644
index a3f447fee..000000000
--- a/component/http/errors_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package http
-
-import (
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestError_WithHeaders(t *testing.T) {
-	header := "Retry-After"
-	err := NewErrorWithCodeAndPayload(http.StatusTooManyRequests, "wait").
-		WithHeaders(map[string]string{header: "1628002707"})
-	assert.EqualError(t, err, "HTTP error with code: 429 payload: wait")
-	assert.Equal(t, 429, err.code)
-	assert.Equal(t, "1628002707", err.headers[header])
-}
-
-func TestValidationError(t *testing.T) {
-	err := NewValidationError()
-	assert.EqualError(t, err, "HTTP error with code: 400 payload: Bad Request")
-	assert.Equal(t, 400, err.code)
-}
-
-func TestValidationErrorWithPayload(t *testing.T) {
-	err := NewValidationErrorWithPayload("test")
-	assert.EqualError(t, err, "HTTP error with code: 400 payload: test")
-	assert.Equal(t, 400, err.code)
-}
-
-func TestUnauthorizedError(t *testing.T) {
-	err := NewUnauthorizedError()
-	assert.EqualError(t, err, "HTTP error with code: 401 payload: Unauthorized")
-	assert.Equal(t, 401, err.code)
-}
-
-func TestUnauthorizedErrorWithPayload(t *testing.T) {
-	err := NewUnauthorizedErrorWithPayload("test")
-	assert.EqualError(t, err, "HTTP error with code: 401 payload: test")
-	assert.Equal(t, 401, err.code)
-}
-
-func TestForbiddenError(t *testing.T) {
-	err := NewForbiddenError()
-	assert.EqualError(t, err, "HTTP error with code: 403 payload: Forbidden")
-	assert.Equal(t, 403, err.code)
-}
-
-func TestForbiddenErrorWithPayload(t *testing.T) {
-	err := NewForbiddenErrorWithPayload("test")
-	assert.EqualError(t, err, "HTTP error with code: 403 payload: test")
-	assert.Equal(t, 403, err.code)
-}
-
-func TestNotFoundError(t *testing.T) {
-	err := NewNotFoundError()
-	assert.EqualError(t, err, "HTTP error with code: 404 payload: Not Found")
-	assert.Equal(t, 404, err.code)
-}
-
-func TestNotFoundErrorWithPayload(t *testing.T) {
-	err := NewNotFoundErrorWithPayload("test")
-	assert.EqualError(t, err, "HTTP error with code: 404 payload: test")
-	assert.Equal(t, 404, err.code)
-}
-
-func TestServiceUnavailableError(t *testing.T) {
-	err := NewServiceUnavailableError()
-	assert.EqualError(t, err, "HTTP error with code: 503 payload: Service Unavailable")
-	assert.Equal(t, 503, err.code)
-}
-
-func TestServiceUnavailableErrorWithPayload(t *testing.T) {
-	err := NewServiceUnavailableErrorWithPayload("test")
-	assert.EqualError(t, err, "HTTP error with code: 503 payload: test")
-	assert.Equal(t, 503, err.code)
-}
-
-func TestNewError(t *testing.T) {
-	err := NewError()
-	assert.EqualError(t, err, "HTTP error with code: 500 payload: Internal Server Error")
-	assert.Equal(t, 500, err.code)
-}
-
-func TestNewErrorWithCodeAndPayload(t *testing.T) {
-	err := NewErrorWithCodeAndPayload(409, "Conflict")
-	assert.EqualError(t, err, "HTTP error with code: 409 payload: Conflict")
-	assert.Equal(t, 409, err.code)
-}
-
-func TestNewErrorWithCodeAndNoPayload(t *testing.T) {
-	err := NewErrorWithCodeAndPayload(409, nil)
-	assert.EqualError(t, err, "HTTP error with code: 409")
-	assert.Equal(t, 409, err.code)
-}
diff --git a/component/http/handler_test.go b/component/http/handler_test.go
deleted file mode 100644
index af3601d3c..000000000
--- a/component/http/handler_test.go
+++ /dev/null
@@ -1,479 +0,0 @@
-package http
-
-import (
-	"context"
-	"errors"
-	"io/ioutil"
-	"net/http"
-	"net/http/httptest"
-	"reflect"
-	"testing"
-
-	"github.com/beatlabs/patron/correlation"
-	"github.com/beatlabs/patron/encoding"
-	"github.com/beatlabs/patron/encoding/json"
-	"github.com/beatlabs/patron/encoding/protobuf"
-	"github.com/julienschmidt/httprouter"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-	"golang.org/x/exp/slog"
-)
-
-func Test_extractFields(t *testing.T) {
-	r, err := http.NewRequest("GET", "/test?value1=1&value2=2", nil)
-	assert.NoError(t, err)
-	f := extractFields(r)
-	assert.Len(t, f, 2)
-	assert.Equal(t, "1", f["value1"])
-	assert.Equal(t, "2", f["value2"])
-}
-
-func Test_extractHeaders(t *testing.T) {
-	r, err := http.NewRequest("GET", "/test", nil)
-	require.NoError(t, err)
-	r.Header.Set("X-HEADER-1", "all capsssss")
-	r.Header.Set("X-HEADER-1", "all caps")
-	r.Header.Set("x-Header-2", "all lower")
-	r.Header.Set("X-hEadEr-3", "all mixed")
-	r.Header.Set("X-ACME", "")
-	assert.NoError(t, err)
-	h := extractHeaders(r.Header)
-	assert.Len(t, h, 3)
-	assert.Equal(t, "all caps", h["X-HEADER-1"])
-	assert.Equal(t, "all lower", h["X-HEADER-2"])
-	assert.Equal(t, "all mixed", h["X-HEADER-3"])
-}
-
-func Test_determineEncoding(t *testing.T) {
-	type args struct {
-		req *http.Request
-	}
-	tests := []struct {
-		name    string
-		args    args
-		decode  encoding.DecodeFunc
-		encode  encoding.EncodeFunc
-		ct      string
-		wantErr bool
-	}{
-		{"success json", args{req: request(t, json.Type, json.TypeCharset)}, json.Decode, json.Encode, json.TypeCharset, false},
-		{"success json, missing accept", args{req: request(t, json.Type, "")}, json.Decode, json.Encode, json.TypeCharset, false},
-		{"success json, missing content type", args{req: request(t, "", json.Type)}, json.Decode, json.Encode, json.TypeCharset, false},
-		{"success protobuf", args{req: request(t, protobuf.Type, protobuf.TypeGoogle)}, protobuf.Decode, protobuf.Encode, protobuf.Type, false},
-		{"success protobuf, missing accept", args{req: request(t, protobuf.Type, "")}, protobuf.Decode, protobuf.Encode, protobuf.Type, false},
-		{"success protobuf, missing content type", args{req: request(t, "", protobuf.Type)}, protobuf.Decode, protobuf.Encode, protobuf.Type, false},
-		{"wrong accept", args{req: request(t, json.Type, "xxx")}, nil, nil, json.TypeCharset, true},
-		{"missing content Header, defaults json", args{req: request(t, "", json.TypeCharset)}, json.Decode, json.Encode, json.TypeCharset, false},
-		{"missing headers, defaults json", args{req: request(t, "", "")}, json.Decode, json.Encode, json.TypeCharset, false},
-		{"accept */*, defaults to json", args{req: request(t, json.TypeCharset, "*/*")}, json.Decode, json.Encode, json.TypeCharset, false},
-		{"wrong content", args{req: request(t, "application/xml", json.TypeCharset)}, nil, nil, json.TypeCharset, true},
-		{"multi-value accept", args{req: request(t, json.TypeCharset, "application/json, */*")}, json.Decode, json.Encode, json.TypeCharset, false},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			ct, got, got1, err := determineEncoding(tt.args.req.Header)
-			if tt.wantErr {
-				assert.Error(t, err)
-				assert.Nil(t, got)
-				assert.Nil(t, got1)
-				assert.Empty(t, ct)
-			} else {
-				assert.NoError(t, err)
-				assert.NotNil(t, got)
-				assert.NotNil(t, got1)
-				assert.Equal(t, tt.ct, ct)
-			}
-		})
-	}
-}
-
-func Test_getMultiValueHeaders(t *testing.T) {
-	tests := []struct {
-		name            string
-		headers         string
-		expectedHeaders []string
-	}{
-		{"empty string", "", []string{""}},
-		{"single header", "*/*", []string{"*/*"}},
-		{"comma separated multi(2) header with space", "application/json, */*", []string{"application/json", "*/*"}},
-		{"comma separated multi(2) header with multiple spaces", " application/json, */* ", []string{"application/json", "*/*"}},
-		{"comma separated multi(2) header with no space", "application/json,*/*", []string{"application/json", "*/*"}},
-		{"comma separated multi(2) header", "application/json,*/*", []string{"application/json", "*/*"}},
-		{"comma separated multi(3) header", "application/json,*/*,application/xml", []string{"application/json", "*/*", "application/xml"}},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			newHeaders := getMultiValueHeaders(tt.headers)
-			assert.Equal(t, tt.expectedHeaders, newHeaders)
-		})
-	}
-}
-
-func request(t *testing.T, contentType, accept string) *http.Request {
-	req, err := http.NewRequest(http.MethodGet, "/", nil)
-	require.NoError(t, err)
-	if contentType != "" {
-		req.Header.Set(encoding.ContentTypeHeader, contentType)
-	}
-	if accept != "" {
-		req.Header.Set(encoding.AcceptHeader, accept)
-	}
-	return req
-}
-
-func Test_handleSuccess(t *testing.T) {
-	t.Parallel()
-	get, err := http.NewRequest(http.MethodGet, "/", nil)
-	assert.NoError(t, err)
-	post, err := http.NewRequest(http.MethodPost, "/", nil)
-	assert.NoError(t, err)
-	jsonRsp := NewResponse(struct {
-		Name    string
-		Address string
-	}{"Sotiris", "Athens"})
-	jsonEncodeFailRsp := NewResponse(struct {
-		Name    chan bool
-		Address string
-	}{nil, "Athens"})
-
-	type args struct {
-		req *http.Request
-		rsp *Response
-		enc encoding.EncodeFunc
-	}
-	tests := []struct {
-		name           string
-		args           args
-		expectedStatus int
-		wantErr        bool
-	}{
-		{"GET No Content success", args{req: get, rsp: nil, enc: nil}, http.StatusNoContent, false},
-		{"GET OK success", args{req: get, rsp: jsonRsp, enc: json.Encode}, http.StatusOK, false},
-		{"POST Created success", args{req: post, rsp: jsonRsp, enc: json.Encode}, http.StatusCreated, false},
-		{"Encode failure", args{req: post, rsp: jsonEncodeFailRsp, enc: json.Encode}, http.StatusCreated, true},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-			rsp := httptest.NewRecorder()
-
-			err := handleSuccess(rsp, tt.args.req, tt.args.rsp, tt.args.enc)
-			if tt.wantErr {
-				assert.Error(t, err)
-			} else {
-				assert.NoError(t, err)
-				assert.Equal(t, tt.expectedStatus, rsp.Code)
-			}
-		})
-	}
-}
-
-func Test_handleError(t *testing.T) {
-	type args struct {
-		err error
-		enc encoding.EncodeFunc
-	}
-	tests := []struct {
-		name            string
-		args            args
-		expectedCode    int
-		expectedHeaders map[string]string
-	}{
-		{name: "bad request", args: args{err: NewValidationError(), enc: json.Encode}, expectedCode: http.StatusBadRequest},
-		{name: "too many requests with header", args: args{err: NewErrorWithCodeAndPayload(http.StatusTooManyRequests, "test").WithHeaders(map[string]string{"Retry-After": "1628027625"}), enc: json.Encode}, expectedCode: http.StatusTooManyRequests, expectedHeaders: map[string]string{"Retry-After": "1628027625"}},
-		{name: "unauthorized request", args: args{err: NewUnauthorizedError(), enc: json.Encode}, expectedCode: http.StatusUnauthorized},
-		{name: "forbidden request", args: args{err: NewForbiddenError(), enc: json.Encode}, expectedCode: http.StatusForbidden},
-		{name: "not found error", args: args{err: NewNotFoundError(), enc: json.Encode}, expectedCode: http.StatusNotFound},
-		{name: "service unavailable error", args: args{err: NewServiceUnavailableError(), enc: json.Encode}, expectedCode: http.StatusServiceUnavailable},
-		{name: "internal server error", args: args{err: NewError(), enc: json.Encode}, expectedCode: http.StatusInternalServerError},
-		{name: "default error", args: args{err: errors.New("test"), enc: json.Encode}, expectedCode: http.StatusInternalServerError},
-		{name: "Payload encoding error", args: args{err: NewErrorWithCodeAndPayload(http.StatusBadRequest, make(chan int)), enc: json.Encode}, expectedCode: http.StatusInternalServerError},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			rsp := httptest.NewRecorder()
-			handleError(slog.With(), rsp, tt.args.enc, tt.args.err)
-			assert.Equal(t, tt.expectedCode, rsp.Code)
-			for k, v := range tt.expectedHeaders {
-				assert.Equal(t, v, rsp.Header().Get(k))
-			}
-		})
-	}
-}
-
-func Test_getOrSetCorrelationID(t *testing.T) {
-	t.Parallel()
-	withID := http.Header{correlation.HeaderID: []string{"123"}}
-	withoutID := http.Header{correlation.HeaderID: []string{}}
-	withEmptyID := http.Header{correlation.HeaderID: []string{""}}
-	missingHeader := http.Header{}
-	type args struct {
-		hdr http.Header
-	}
-	tests := map[string]struct {
-		args args
-	}{
-		"with id":        {args: args{hdr: withID}},
-		"without id":     {args: args{hdr: withoutID}},
-		"with empty id":  {args: args{hdr: withEmptyID}},
-		"missing Header": {args: args{hdr: missingHeader}},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			assert.NotEmpty(t, getOrSetCorrelationID(tt.args.hdr))
-			assert.NotEmpty(t, tt.args.hdr[correlation.HeaderID][0])
-		})
-	}
-}
-
-type testHandler struct {
-	err  bool
-	resp interface{}
-}
-
-func (th testHandler) Process(_ context.Context, _ *Request) (*Response, error) {
-	if th.err {
-		return nil, errors.New("TEST")
-	}
-	return NewResponse(th.resp), nil
-}
-
-func Test_handler(t *testing.T) {
-	errReq, err := http.NewRequest(http.MethodGet, "/", nil)
-	require.NoError(t, err)
-	errReq.Header.Set(encoding.ContentTypeHeader, "xml")
-	require.NoError(t, err)
-
-	req, err := http.NewRequest(http.MethodGet, "/", nil)
-	require.NoError(t, err)
-
-	req.Header.Set(encoding.ContentTypeHeader, json.Type)
-	req.Header.Set(encoding.AcceptHeader, json.Type)
-
-	// success handling
-	// failure handling
-	type args struct {
-		req *http.Request
-		hnd ProcessorFunc
-	}
-	tests := []struct {
-		name         string
-		args         args
-		expectedCode int
-	}{
-		{
-			name:         "unsupported content type",
-			args:         args{req: errReq, hnd: nil},
-			expectedCode: http.StatusUnsupportedMediaType,
-		},
-		{
-			name:         "success handling",
-			args:         args{req: req, hnd: testHandler{err: false, resp: "test"}.Process},
-			expectedCode: http.StatusOK,
-		},
-		{
-			name:         "error handling",
-			args:         args{req: req, hnd: testHandler{err: true, resp: "test"}.Process},
-			expectedCode: http.StatusInternalServerError,
-		},
-		{
-			name:         "success handling failed due to encoding",
-			args:         args{req: req, hnd: testHandler{err: false, resp: make(chan bool)}.Process},
-			expectedCode: http.StatusInternalServerError,
-		},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			rsp := httptest.NewRecorder()
-			handler(tt.args.hnd).ServeHTTP(rsp, tt.args.req)
-			assert.Equal(t, tt.expectedCode, rsp.Code)
-		})
-	}
-}
-
-func Test_prepareResponse(t *testing.T) {
-	rsp := httptest.NewRecorder()
-	prepareResponse(rsp, json.TypeCharset)
-	assert.Equal(t, json.TypeCharset, rsp.Header().Get(encoding.ContentTypeHeader))
-}
-
-func Test_extractParams(t *testing.T) {
-	req, err := http.NewRequest(http.MethodGet, "/users/1/status", nil)
-	assert.NoError(t, err)
-	req.Header.Set(encoding.ContentTypeHeader, json.Type)
-	req.Header.Set(encoding.AcceptHeader, json.Type)
-	var fields map[string]string
-
-	proc := func(_ context.Context, req *Request) (*Response, error) {
-		fields = req.Fields
-		return nil, nil
-	}
-
-	router := httprouter.New()
-	route, err := NewRouteBuilder("/users/:id/status", proc).MethodGet().Build()
-	assert.NoError(t, err)
-	router.HandlerFunc(route.method, route.path, route.handler)
-	router.ServeHTTP(httptest.NewRecorder(), req)
-	assert.Equal(t, "1", fields["id"])
-}
-
-func Test_fileserverHandler(t *testing.T) {
-	router := httprouter.New()
-	path := "/frontend/*path"
-	route, err := NewFileServer(path, "testdata", "testdata/index.html").Build()
-	require.NoError(t, err)
-	router.HandlerFunc(route.method, route.path, route.handler)
-
-	assert.Equal(t, path, route.Path())
-	assert.Equal(t, http.MethodGet, route.Method())
-	tests := map[string]struct {
-		expectedResponse string
-		path             string
-	}{
-		"success":  {path: "/frontend/existing.html", expectedResponse: "existing"},
-		"fallback": {path: "/frontend/missing-file", expectedResponse: "fallback"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			// the only way to test do we get the same handler that we provided initially, is to run it explicitly,
-			// since all we have in Route itself is a wrapper function
-			req, err := http.NewRequest(http.MethodGet, tt.path, nil)
-			require.NoError(t, err)
-
-			wr := httptest.NewRecorder()
-			router.ServeHTTP(wr, req)
-			br, err := ioutil.ReadAll(wr.Body)
-			require.NoError(t, err)
-
-			assert.Equal(t, tt.expectedResponse, string(br))
-		})
-	}
-}
-
-func Test_extractParamsRawRoute(t *testing.T) {
-	r, err := http.NewRequest(http.MethodGet, "/users/42/status/online", nil)
-	assert.NoError(t, err)
-	r.Header.Set(encoding.ContentTypeHeader, json.Type)
-	r.Header.Set(encoding.AcceptHeader, json.Type)
-	var fields map[string]string
-
-	proc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		fields = ExtractParams(r)
-	})
-
-	router := httprouter.New()
-	route, err := NewRawRouteBuilder("/users/:id/status/:status", proc).MethodGet().Build()
-	assert.NoError(t, err)
-	router.HandlerFunc(route.method, route.path, route.handler)
-	router.ServeHTTP(httptest.NewRecorder(), r)
-
-	assert.Equal(t, "42", fields["id"])
-	assert.Equal(t, "online", fields["status"])
-}
-
-func Test_getSingleHeaderEncoding(t *testing.T) {
-	testcases := []struct {
-		header string
-		ct     string
-		dec    encoding.DecodeFunc
-		enc    encoding.EncodeFunc
-		err    error
-	}{
-		{
-			header: json.Type,
-			ct:     json.TypeCharset,
-			dec:    json.Decode,
-			enc:    json.Encode,
-			err:    nil,
-		},
-		{
-			header: json.TypeCharset,
-			ct:     json.TypeCharset,
-			dec:    json.Decode,
-			enc:    json.Encode,
-			err:    nil,
-		},
-		{
-			header: "*/*", // json as default (?)
-			ct:     json.TypeCharset,
-			dec:    json.Decode,
-			enc:    json.Encode,
-			err:    nil,
-		},
-		{
-			header: "*",
-			ct:     json.TypeCharset,
-			dec:    json.Decode,
-			enc:    json.Encode,
-			err:    nil,
-		},
-		{
-			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding#:~:text=This%20value%20is%20always%20considered%20as%20acceptable%2C%20even%20if%20omitted
-			header: "identity",
-			ct:     json.TypeCharset,
-			dec:    json.Decode,
-			enc:    json.Encode,
-			err:    nil,
-		},
-		{
-			header: "*/*;q=0.8",
-			ct:     json.TypeCharset,
-			dec:    json.Decode,
-			enc:    json.Encode,
-			err:    nil,
-		},
-		{
-			header: protobuf.Type,
-			ct:     protobuf.Type,
-			dec:    protobuf.Decode,
-			enc:    protobuf.Encode,
-			err:    nil,
-		},
-		{
-			header: protobuf.TypeGoogle,
-			ct:     protobuf.Type,
-			dec:    protobuf.Decode,
-			enc:    protobuf.Encode,
-			err:    nil,
-		},
-		{
-			header: "text/html",
-			ct:     "",
-			dec:    nil,
-			enc:    nil,
-			err:    errors.New("accept header not supported"),
-		},
-		{
-			header: "garbage",
-			ct:     "",
-			dec:    nil,
-			enc:    nil,
-			err:    errors.New("accept header not supported"),
-		},
-	}
-
-	for _, tc := range testcases {
-		tc := tc
-		t.Run(tc.header, func(t *testing.T) {
-			ct, dec, enc, err := getSingleHeaderEncoding(tc.header)
-			assert.Equal(t, tc.err, err)
-			assert.Equal(t, tc.ct, ct)
-
-			if reflect.ValueOf(tc.dec).Pointer() != reflect.ValueOf(dec).Pointer() {
-				t.Fatalf("Invalid decoder\n\texpected: %v\n\treceived: %v", tc.dec, dec)
-			}
-
-			if reflect.ValueOf(tc.enc).Pointer() != reflect.ValueOf(enc).Pointer() {
-				t.Fatalf("Invalid encoder\n\texpected: %v\n\treceived: %v", tc.dec, dec)
-			}
-		})
-	}
-}
diff --git a/component/http/http_test.go b/component/http/http_test.go
deleted file mode 100644
index b2f155ac1..000000000
--- a/component/http/http_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package http
-
-import (
-	"bytes"
-	"testing"
-
-	"github.com/beatlabs/patron/encoding/json"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNewRequest(t *testing.T) {
-	req := NewRequest(nil, nil, nil, nil)
-	assert.NotNil(t, req)
-}
-
-func TestRequest_Decode(t *testing.T) {
-	j, err := json.Encode("string")
-	assert.NoError(t, err)
-	b := bytes.NewBuffer(j)
-	req := NewRequest(nil, b, nil, json.Decode)
-	assert.NotNil(t, req)
-	var data string
-	err = req.Decode(&data)
-	assert.NoError(t, err)
-	assert.Equal(t, "string", data)
-}
-
-func TestNewResponse(t *testing.T) {
-	rsp := NewResponse("test")
-	assert.NotNil(t, rsp)
-	assert.IsType(t, "test", rsp.Payload)
-}
diff --git a/component/http/metric_test.go b/component/http/metric_test.go
deleted file mode 100644
index 2b6588457..000000000
--- a/component/http/metric_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package http
-
-import (
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_metricRoute(t *testing.T) {
-	route, err := metricRoute().Build()
-	assert.NoError(t, err)
-	assert.Equal(t, http.MethodGet, route.method)
-	assert.Equal(t, "/metrics", route.path)
-	assert.NotNil(t, route.handler)
-}
diff --git a/component/http/v2/observability_test.go b/component/http/observability_test.go
similarity index 99%
rename from component/http/v2/observability_test.go
rename to component/http/observability_test.go
index 502586faa..3f30e5f30 100644
--- a/component/http/v2/observability_test.go
+++ b/component/http/observability_test.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"fmt"
diff --git a/component/http/profiling_test.go b/component/http/profiling_test.go
deleted file mode 100644
index c7ce56687..000000000
--- a/component/http/profiling_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package http
-
-import (
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_PprofHandlers(t *testing.T) {
-	mux := http.NewServeMux()
-
-	rr := profilingRoutes()
-
-	for _, r := range rr {
-		route, err := r.Build()
-		assert.NoError(t, err)
-		mux.HandleFunc(route.path, route.handler)
-	}
-
-	server := httptest.NewServer(mux)
-	defer server.Close()
-
-	tests := []struct {
-		name string
-		path string
-		want int
-	}{
-		{"index", "/debug/pprof/", 200},
-		{"cmdline", "/debug/pprof/allocs/", 200},
-		{"cmdline", "/debug/pprof/cmdline/", 200},
-		{"profile", "/debug/pprof/profile/?seconds=1", 200},
-		{"symbol", "/debug/pprof/symbol/", 200},
-		{"trace", "/debug/pprof/trace/?seconds=0.1", 200},
-		{"heap", "/debug/pprof/heap/", 200},
-		{"goroutine", "/debug/pprof/goroutine/", 200},
-		{"block", "/debug/pprof/block/", 200},
-		{"threadcreate", "/debug/pprof/threadcreate/", 200},
-		{"mutex", "/debug/pprof/mutex/", 200},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			resp, err := http.Get(fmt.Sprintf("%s/%s", server.URL, tt.path))
-			assert.NoError(t, err)
-			assert.Equal(t, tt.want, resp.StatusCode)
-		})
-	}
-}
diff --git a/component/http/ready_test.go b/component/http/ready_test.go
deleted file mode 100644
index 93f152214..000000000
--- a/component/http/ready_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package http
-
-import (
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_readyCheckRoute(t *testing.T) {
-	tests := []struct {
-		name string
-		rcf  ReadyCheckFunc
-		want int
-	}{
-		{"ready", func() ReadyStatus { return Ready }, http.StatusOK},
-		{"notReady", func() ReadyStatus { return NotReady }, http.StatusServiceUnavailable},
-		{"default", func() ReadyStatus { return 10 }, http.StatusOK},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			r, err := readyCheckRoute(tt.rcf).Build()
-			assert.NoError(t, err)
-			resp := httptest.NewRecorder()
-			req, err := http.NewRequest("GET", "/alive", nil)
-			assert.NoError(t, err)
-			r.handler(resp, req)
-			assert.Equal(t, tt.want, resp.Code)
-		})
-	}
-}
diff --git a/component/http/route_cache_test.go b/component/http/route_cache_test.go
deleted file mode 100644
index bd734027f..000000000
--- a/component/http/route_cache_test.go
+++ /dev/null
@@ -1,595 +0,0 @@
-package http
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"sync"
-	"sync/atomic"
-	"testing"
-	"time"
-
-	"github.com/beatlabs/patron/cache"
-	httpclient "github.com/beatlabs/patron/client/http"
-	httpcache "github.com/beatlabs/patron/component/http/cache"
-	"github.com/beatlabs/patron/component/http/middleware"
-	"github.com/beatlabs/patron/encoding"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-type cacheState struct {
-	setOps int
-	getOps int
-	size   int
-}
-
-type builderOperation func(routeBuilder *RouteBuilder) *RouteBuilder
-
-type arg struct {
-	bop builderOperation
-	age httpcache.Age
-	err bool
-}
-
-func TestCachingMiddleware(t *testing.T) {
-	getRequest, err := http.NewRequest("GET", "/test", nil)
-	assert.NoError(t, err)
-
-	postRequest, err := http.NewRequest("POST", "/test", nil)
-	assert.NoError(t, err)
-
-	type args struct {
-		next http.Handler
-		mws  []middleware.Func
-	}
-
-	testingCache := newTestingCache()
-	testingCache.instant = httpcache.NowSeconds
-
-	routeCache, errs := httpcache.NewRouteCache(testingCache, httpcache.Age{Max: 1 * time.Second})
-	assert.Empty(t, errs)
-
-	cachingMiddleware, err := middleware.NewCaching(routeCache)
-	require.NoError(t, err)
-
-	tests := []struct {
-		name         string
-		args         args
-		r            *http.Request
-		expectedCode int
-		expectedBody string
-		cacheState   cacheState
-	}{
-		{
-			"caching middleware with POST request",
-			args{next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				w.WriteHeader(202)
-				i, err := w.Write([]byte{1, 2, 3, 4})
-				assert.NoError(t, err)
-				assert.Equal(t, 4, i)
-			}), mws: []middleware.Func{cachingMiddleware}},
-			postRequest, 202, "\x01\x02\x03\x04",
-			cacheState{},
-		},
-		{
-			"caching middleware with GET request",
-			args{next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-				w.WriteHeader(200)
-				i, err := w.Write([]byte{1, 2, 3, 4})
-				assert.NoError(t, err)
-				assert.Equal(t, 4, i)
-			}), mws: []middleware.Func{cachingMiddleware}},
-			getRequest, 200, "\x01\x02\x03\x04",
-			cacheState{
-				setOps: 1,
-				getOps: 1,
-				size:   1,
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			rc := httptest.NewRecorder()
-			rw := &stubResponseWriter{writer: rc}
-			tt.args.next = middleware.Chain(tt.args.next, tt.args.mws...)
-			tt.args.next.ServeHTTP(rw, tt.r)
-			assert.Equal(t, tt.expectedCode, rw.Status())
-			assert.Equal(t, tt.expectedBody, rc.Body.String())
-			assertCacheState(t, *testingCache, tt.cacheState)
-		})
-	}
-}
-
-func TestNewRouteBuilder_WithCache(t *testing.T) {
-	args := []arg{
-		{
-			bop: func(routeBuilder *RouteBuilder) *RouteBuilder {
-				return routeBuilder.MethodGet()
-			},
-			age: httpcache.Age{Max: 10},
-		},
-		// error with '0' ttl
-		{
-			bop: func(routeBuilder *RouteBuilder) *RouteBuilder {
-				return routeBuilder.MethodGet()
-			},
-			age: httpcache.Age{Min: 10, Max: 1},
-			err: true,
-		},
-		// error for POST method
-		{
-			bop: func(routeBuilder *RouteBuilder) *RouteBuilder {
-				return routeBuilder.MethodPost()
-			},
-			age: httpcache.Age{Max: 10},
-			err: true,
-		},
-	}
-
-	c := newTestingCache()
-
-	processor := func(context context.Context, request *Request) (response *Response, e error) {
-		return nil, nil
-	}
-
-	handler := func(writer http.ResponseWriter, i *http.Request) {
-	}
-
-	for _, arg := range args {
-		assertRouteBuilder(t, arg, NewRouteBuilder("/", processor), c)
-
-		assertRouteBuilder(t, arg, NewRawRouteBuilder("/", handler), c)
-	}
-}
-
-func assertRouteBuilder(t *testing.T, arg arg, routeBuilder *RouteBuilder, cache cache.TTLCache) {
-	routeBuilder.WithRouteCache(cache, arg.age)
-
-	if arg.bop != nil {
-		routeBuilder = arg.bop(routeBuilder)
-	}
-
-	route, err := routeBuilder.Build()
-	assert.NotNil(t, route)
-
-	if arg.err {
-		assert.Error(t, err)
-	} else {
-		assert.NoError(t, err)
-	}
-}
-
-func TestRouteCacheImplementation_WithSingleRequest(t *testing.T) {
-	ce := make(chan error, 1)
-
-	cc := newTestingCache()
-	cc.instant = httpcache.NowSeconds
-
-	var executions uint32
-
-	preWrapper := newMiddlewareWrapper(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("pre-middleware-header", "pre")
-	})
-
-	postWrapper := newMiddlewareWrapper(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("post-middleware-header", "post")
-	})
-
-	routeBuilder := NewRouteBuilder("/path", func(context context.Context, request *Request) (response *Response, e error) {
-		atomic.AddUint32(&executions, 1)
-		newResponse := NewResponse("body")
-		newResponse.Header["Custom-Header"] = "11"
-		return newResponse, nil
-	}).
-		WithRouteCache(cc, httpcache.Age{Max: 10 * time.Second}).
-		WithMiddlewares(preWrapper.middleware, postWrapper.middleware).
-		MethodGet()
-
-	ctx, cln := context.WithTimeout(context.Background(), 5*time.Second)
-
-	port := 50023
-	runRoute(ctx, t, routeBuilder, ce, port)
-
-	assertResponse(t, []http.Response{
-		{
-			Header: map[string][]string{
-				httpcache.HeaderCacheControl: {"max-age=10"},
-				"Content-Type":               {"application/json; charset=utf-8"},
-				"Content-Length":             {"6"},
-				"Post-Middleware-Header":     {"post"},
-				"Pre-Middleware-Header":      {"pre"},
-				"Custom-Header":              {"11"},
-			},
-			Body: &bodyReader{body: "\"body\""},
-		},
-		{
-			Header: map[string][]string{
-				httpcache.HeaderCacheControl: {"max-age=10"},
-				"Content-Type":               {"application/json; charset=utf-8"},
-				"Content-Length":             {"6"},
-				"Post-Middleware-Header":     {"post"},
-				"Pre-Middleware-Header":      {"pre"},
-				"Custom-Header":              {"11"},
-			},
-			Body: &bodyReader{body: "\"body\""},
-		},
-	}, port)
-
-	assertCacheState(t, *cc, cacheState{
-		setOps: 1,
-		getOps: 2,
-		size:   1,
-	})
-
-	assert.Equal(t, 2, preWrapper.invocations)
-	assert.Equal(t, 2, postWrapper.invocations)
-
-	assert.Equal(t, executions, uint32(1))
-	cln()
-	assert.NoError(t, <-ce)
-}
-
-func TestRouteCacheAsMiddleware_WithSingleRequest(t *testing.T) {
-	ce := make(chan error, 1)
-
-	cc := newTestingCache()
-	cc.instant = httpcache.NowSeconds
-
-	var executions uint32
-
-	preWrapper := newMiddlewareWrapper(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("pre-middleware-header", "pre")
-	})
-
-	postWrapper := newMiddlewareWrapper(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("post-middleware-header", "post")
-	})
-
-	routeCache, errs := httpcache.NewRouteCache(cc, httpcache.Age{Max: 10 * time.Second})
-	assert.Empty(t, errs)
-	cachingMiddleware, err := middleware.NewCaching(routeCache)
-	require.NoError(t, err)
-
-	routeBuilder := NewRouteBuilder("/path", func(context context.Context, request *Request) (response *Response, e error) {
-		atomic.AddUint32(&executions, 1)
-		newResponse := NewResponse("body")
-		newResponse.Header["internal-handler-header"] = "header"
-		return newResponse, nil
-	}).
-		WithMiddlewares(
-			preWrapper.middleware,
-			cachingMiddleware,
-			postWrapper.middleware).
-		MethodGet()
-
-	ctx, cln := context.WithTimeout(context.Background(), 5*time.Second)
-
-	port := 50023
-	runRoute(ctx, t, routeBuilder, ce, port)
-
-	assertResponse(t, []http.Response{
-		{
-			Header: map[string][]string{
-				httpcache.HeaderCacheControl: {"max-age=10"},
-				"Content-Type":               {"application/json; charset=utf-8"},
-				"Content-Length":             {"6"},
-				"Post-Middleware-Header":     {"post"},
-				"Pre-Middleware-Header":      {"pre"},
-				"Internal-Handler-Header":    {"header"},
-			},
-			Body: &bodyReader{body: "\"body\""},
-		},
-		{
-			Header: map[string][]string{
-				httpcache.HeaderCacheControl: {"max-age=10"},
-				"Content-Type":               {"application/json; charset=utf-8"},
-				"Post-Middleware-Header":     {"post"},
-				"Pre-Middleware-Header":      {"pre"},
-				"Content-Length":             {"6"},
-				"Internal-Handler-Header":    {"header"},
-			},
-			Body: &bodyReader{body: "\"body\""},
-		},
-	}, port)
-
-	assertCacheState(t, *cc, cacheState{
-		setOps: 1,
-		getOps: 2,
-		size:   1,
-	})
-
-	assert.Equal(t, 2, preWrapper.invocations)
-	// NOTE : the post middleware is not executed, as it s hidden behind the cache
-	assert.Equal(t, 1, postWrapper.invocations)
-
-	assert.Equal(t, executions, uint32(1))
-
-	cln()
-	assert.NoError(t, <-ce)
-}
-
-type middlewareWrapper struct {
-	middleware  middleware.Func
-	invocations int
-}
-
-func newMiddlewareWrapper(middlewareFunc func(w http.ResponseWriter, r *http.Request)) *middlewareWrapper {
-	wrapper := &middlewareWrapper{}
-	wrapper.middleware = func(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			wrapper.invocations++
-			middlewareFunc(w, r)
-			next.ServeHTTP(w, r)
-		})
-	}
-	return wrapper
-}
-
-func TestRawRouteCacheImplementation_WithSingleRequest(t *testing.T) {
-	ce := make(chan error, 1)
-
-	cc := newTestingCache()
-	cc.instant = httpcache.NowSeconds
-
-	var executions uint32
-
-	preWrapper := newMiddlewareWrapper(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("pre-middleware-header", "pre")
-	})
-
-	postWrapper := newMiddlewareWrapper(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("post-middleware-header", "post")
-	})
-
-	routeBuilder := NewRawRouteBuilder("/path", func(writer http.ResponseWriter, request *http.Request) {
-		atomic.AddUint32(&executions, 1)
-		i, err := writer.Write([]byte("\"body\""))
-		writer.Header().Set("internal-handler-header", "header")
-		assert.NoError(t, err)
-		assert.True(t, i > 0)
-	}).
-		WithRouteCache(cc, httpcache.Age{Max: 10 * time.Second}).
-		WithMiddlewares(preWrapper.middleware, postWrapper.middleware).
-		MethodGet()
-
-	ctx, cln := context.WithTimeout(context.Background(), 5*time.Second)
-
-	port := 50024
-	runRoute(ctx, t, routeBuilder, ce, port)
-
-	assertResponse(t, []http.Response{
-		{
-			Header: map[string][]string{
-				httpcache.HeaderCacheControl: {"max-age=10"},
-				"Content-Type":               {"text/plain; charset=utf-8"},
-				"Content-Length":             {"6"},
-				"Post-Middleware-Header":     {"post"},
-				"Pre-Middleware-Header":      {"pre"},
-				"Internal-Handler-Header":    {"header"},
-			},
-			Body: &bodyReader{body: "\"body\""},
-		},
-		{
-			Header: map[string][]string{
-				httpcache.HeaderCacheControl: {"max-age=10"},
-				"Content-Type":               {"text/plain; charset=utf-8"},
-				"Content-Length":             {"6"},
-				"Post-Middleware-Header":     {"post"},
-				"Pre-Middleware-Header":      {"pre"},
-				"Internal-Handler-Header":    {"header"},
-			},
-			Body: &bodyReader{body: "\"body\""},
-		},
-	}, port)
-
-	assertCacheState(t, *cc, cacheState{
-		setOps: 1,
-		getOps: 2,
-		size:   1,
-	})
-
-	assert.Equal(t, 2, preWrapper.invocations)
-	assert.Equal(t, 2, postWrapper.invocations)
-
-	assert.Equal(t, executions, uint32(1))
-
-	cln()
-	assert.NoError(t, <-ce)
-}
-
-type bodyReader struct {
-	body string
-}
-
-func (br *bodyReader) Read(p []byte) (n int, err error) {
-	var c int
-	for i, b := range []byte(br.body) {
-		p[i] = b
-		c = i
-	}
-	return c + 1, nil
-}
-
-func (br *bodyReader) Close() error {
-	// nothing to do
-	return nil
-}
-
-func runRoute(ctx context.Context, t *testing.T, routeBuilder *RouteBuilder, ce chan error, port int) {
-	cmp, err := NewBuilder().WithRoutesBuilder(NewRoutesBuilder().Append(routeBuilder)).WithPort(port).Create()
-
-	assert.NoError(t, err)
-	assert.NotNil(t, cmp)
-
-	go func() {
-		ce <- cmp.Run(ctx)
-		close(ce)
-	}()
-
-	var lwg sync.WaitGroup
-	lwg.Add(1)
-	go func() {
-		cl, err := httpclient.New()
-		assert.NoError(t, err)
-		req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/ready", port), nil)
-		assert.NoError(t, err)
-		for {
-			select {
-			case <-ctx.Done():
-				return
-			default:
-				r, err := cl.Do(req)
-				if err == nil && r != nil {
-					lwg.Done()
-					return
-				}
-			}
-		}
-	}()
-	lwg.Wait()
-}
-
-func assertResponse(t *testing.T, expected []http.Response, port int) {
-	cl, err := httpclient.New()
-	assert.NoError(t, err)
-	req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%d/path", port), nil)
-	require.NoError(t, err)
-	req.Header.Set(encoding.AcceptEncodingHeader, "*") // bypass default HTTP client GZIP-ing our response
-	assert.NoError(t, err)
-
-	for _, expectedResponse := range expected {
-		response, err := cl.Do(req)
-
-		assert.NoError(t, err)
-
-		for k, v := range expectedResponse.Header {
-			assert.Equal(t, v, response.Header[k])
-		}
-		assert.Equal(t, expectedResponse.Header.Get(httpcache.HeaderCacheControl), response.Header.Get(httpcache.HeaderCacheControl))
-		assert.True(t, response.Header.Get(httpcache.HeaderETagHeader) != "")
-		expectedPayload := make([]byte, 6)
-		i, err := expectedResponse.Body.Read(expectedPayload)
-		assert.NoError(t, err)
-
-		responsePayload := make([]byte, 6)
-		j, err := response.Body.Read(responsePayload)
-		assert.Error(t, err)
-
-		assert.Equal(t, i, j)
-		assert.Equal(t, expectedPayload, responsePayload)
-	}
-}
-
-func assertCacheState(t *testing.T, cache testingCache, cacheState cacheState) {
-	assert.Equal(t, cacheState.setOps, cache.setCount)
-	assert.Equal(t, cacheState.getOps, cache.getCount)
-	assert.Equal(t, cacheState.size, cache.size())
-}
-
-type testingCacheEntity struct {
-	v   interface{}
-	ttl int64
-	t0  int64
-}
-
-type testingCache struct {
-	cache    map[string]testingCacheEntity
-	getCount int
-	setCount int
-	getErr   error
-	setErr   error
-	instant  func() int64
-}
-
-func newTestingCache() *testingCache {
-	return &testingCache{cache: make(map[string]testingCacheEntity)}
-}
-
-func (t *testingCache) Get(_ context.Context, key string) (interface{}, bool, error) {
-	t.getCount++
-	if t.getErr != nil {
-		return nil, false, t.getErr
-	}
-	r, ok := t.cache[key]
-	if t.instant()-r.t0 > r.ttl {
-		return nil, false, nil
-	}
-	return r.v, ok, nil
-}
-
-func (t *testingCache) Purge(ctx context.Context) error {
-	for k := range t.cache {
-		_ = t.Remove(ctx, k)
-	}
-	return nil
-}
-
-func (t *testingCache) Remove(_ context.Context, key string) error {
-	delete(t.cache, key)
-	return nil
-}
-
-// Note : this method will effectively not cache anything e.g. testingCacheEntity.t is `0`.
-func (t *testingCache) Set(_ context.Context, key string, value interface{}) error {
-	t.setCount++
-	if t.setErr != nil {
-		return t.getErr
-	}
-	t.cache[key] = testingCacheEntity{
-		v: value,
-	}
-	return nil
-}
-
-func (t *testingCache) SetTTL(_ context.Context, key string, value interface{}, ttl time.Duration) error {
-	t.setCount++
-	if t.setErr != nil {
-		return t.getErr
-	}
-	t.cache[key] = testingCacheEntity{
-		v:   value,
-		ttl: int64(ttl / time.Second),
-		t0:  t.instant(),
-	}
-	return nil
-}
-
-func (t *testingCache) size() int {
-	return len(t.cache)
-}
-
-type stubResponseWriter struct {
-	status              int
-	statusHeaderWritten bool
-	writer              http.ResponseWriter
-}
-
-func (w *stubResponseWriter) Status() int {
-	return w.status
-}
-
-func (w *stubResponseWriter) Header() http.Header {
-	return w.writer.Header()
-}
-
-func (w *stubResponseWriter) Write(d []byte) (int, error) {
-	if !w.statusHeaderWritten {
-		w.status = http.StatusOK
-		w.statusHeaderWritten = true
-	}
-
-	value, err := w.writer.Write(d)
-	if err != nil {
-		return value, err
-	}
-
-	return value, err
-}
-
-func (w *stubResponseWriter) WriteHeader(code int) {
-	w.status = code
-	w.writer.WriteHeader(code)
-	w.statusHeaderWritten = true
-}
diff --git a/component/http/v2/route_option_test.go b/component/http/route_option_test.go
similarity index 99%
rename from component/http/v2/route_option_test.go
rename to component/http/route_option_test.go
index 08ead2368..a35dfe7c7 100644
--- a/component/http/v2/route_option_test.go
+++ b/component/http/route_option_test.go
@@ -1,4 +1,4 @@
-package v2
+package http
 
 import (
 	"net/http"
diff --git a/component/http/route_test.go b/component/http/route_test.go
index 8a4f960e1..2a78dec9e 100644
--- a/component/http/route_test.go
+++ b/component/http/route_test.go
@@ -1,449 +1,196 @@
 package http
 
 import (
-	"context"
-	"encoding/json"
-	"io/ioutil"
+	"errors"
 	"net/http"
-	"net/http/httptest"
 	"testing"
 
-	"github.com/beatlabs/patron/component/http/auth"
-	"github.com/beatlabs/patron/component/http/cache"
-	"github.com/beatlabs/patron/component/http/middleware"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
-type MockAuthenticator struct {
-	success bool
-	err     error
-}
-
-func (mo MockAuthenticator) Authenticate(_ *http.Request) (bool, error) {
-	if mo.err != nil {
-		return false, mo.err
-	}
-	return mo.success, nil
-}
-
-func TestRouteBuilder_WithMethodGet(t *testing.T) {
-	t.Parallel()
-	type args struct {
-		methodExists bool
-	}
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success":               {args: args{}},
-		"method already exists": {args: args{methodExists: true}, expectedErr: "method already set\n"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {})
-			if tt.args.methodExists {
-				rb.MethodGet()
-			}
-			got, err := rb.MethodGet().Build()
-
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-				assert.Equal(t, Route{}, got)
-			} else {
-				assert.NoError(t, err)
-				assert.Equal(t, http.MethodGet, got.method)
-			}
-		})
-	}
-}
-
-func TestRouteBuilder_WithMethodPost(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodPost()
-	assert.Equal(t, http.MethodPost, rb.method)
-}
-
-func TestRouteBuilder_WithMethodPut(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodPut()
-	assert.Equal(t, http.MethodPut, rb.method)
-}
-
-func TestRouteBuilder_WithMethodPatch(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodPatch()
-	assert.Equal(t, http.MethodPatch, rb.method)
-}
-
-func TestRouteBuilder_WithMethodConnect(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodConnect()
-	assert.Equal(t, http.MethodConnect, rb.method)
-}
-
-func TestRouteBuilder_WithMethodDelete(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodDelete()
-	assert.Equal(t, http.MethodDelete, rb.method)
-}
-
-func TestRouteBuilder_WithMethodHead(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodHead()
-	assert.Equal(t, http.MethodHead, rb.method)
-}
-
-func TestRouteBuilder_WithMethodTrace(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodTrace()
-	assert.Equal(t, http.MethodTrace, rb.method)
-}
-
-func TestRouteBuilder_WithMethodOptions(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).MethodOptions()
-	assert.Equal(t, http.MethodOptions, rb.method)
-}
-
-func TestRouteBuilder_WithTrace(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(http.ResponseWriter, *http.Request) {}).WithTrace()
-	assert.True(t, rb.jaegerTrace)
-}
-
-func TestRouteBuilder_WithMiddlewares(t *testing.T) {
-	t.Parallel()
-	mw := func(next http.Handler) http.Handler { return next }
-	mockHandler := func(http.ResponseWriter, *http.Request) {}
-	type fields struct {
-		middlewares []middleware.Func
-	}
-	tests := map[string]struct {
-		fields      fields
-		expectedErr string
-	}{
-		"success":            {fields: fields{middlewares: []middleware.Func{mw}}},
-		"missing middleware": {fields: fields{}, expectedErr: "middlewares are empty"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			rb := NewRawRouteBuilder("/", mockHandler).MethodGet()
-			if len(tt.fields.middlewares) == 0 {
-				rb.WithMiddlewares()
-			} else {
-				rb.WithMiddlewares(tt.fields.middlewares...)
-			}
-
-			if tt.expectedErr != "" {
-				assert.Len(t, rb.errors, 1)
-				assert.EqualError(t, rb.errors[0], tt.expectedErr)
-			} else {
-				assert.Len(t, rb.errors, 0)
-				assert.Len(t, rb.middlewares, 1)
-			}
-		})
-	}
-}
-
-func TestRouteBuilder_WithAuth(t *testing.T) {
+func TestNewRoute(t *testing.T) {
 	t.Parallel()
-	mockAuth := &MockAuthenticator{}
-	mockHandler := func(http.ResponseWriter, *http.Request) {}
-	type fields struct {
-		authenticator auth.Authenticator
-	}
-	tests := map[string]struct {
-		fields      fields
-		expectedErr string
-	}{
-		"success":            {fields: fields{authenticator: mockAuth}},
-		"missing middleware": {fields: fields{}, expectedErr: "authenticator is nil"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			rb := NewRawRouteBuilder("/", mockHandler).WithAuth(tt.fields.authenticator)
-
-			if tt.expectedErr != "" {
-				assert.Len(t, rb.errors, 1)
-				assert.EqualError(t, rb.errors[0], tt.expectedErr)
-			} else {
-				assert.Len(t, rb.errors, 0)
-				assert.NotNil(t, rb.authenticator)
-			}
-		})
-	}
-}
 
-func TestRouteBuilder_WithRateLimiting(t *testing.T) {
-	mockHandler := func(http.ResponseWriter, *http.Request) {}
-	rb := NewRawRouteBuilder("/", mockHandler).WithRateLimiting(1, 1)
-	assert.Len(t, rb.errors, 0)
-	assert.NotNil(t, rb.rateLimiter)
-}
-
-func TestRouteBuilder_WithRouteCacheNil(t *testing.T) {
-	rb := NewRawRouteBuilder("/", func(writer http.ResponseWriter, request *http.Request) {}).
-		WithRouteCache(nil, cache.Age{Max: 1})
-
-	assert.Len(t, rb.errors, 1)
-	assert.EqualError(t, rb.errors[0], "route cache is nil")
-}
-
-func TestRouteBuilder_Build(t *testing.T) {
-	t.Parallel()
-	mockAuth := &MockAuthenticator{}
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	mw := func(next http.Handler) http.Handler { return next }
-	type fields struct {
-		path          string
-		missingMethod bool
-	}
-	tests := map[string]struct {
-		fields      fields
-		expectedErr string
-	}{
-		"success":           {fields: fields{path: "/"}},
-		"missing processor": {fields: fields{path: ""}, expectedErr: "path is empty\n"},
-		"missing method":    {fields: fields{path: "/", missingMethod: true}, expectedErr: "method is missing"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			rb := NewRouteBuilder(tt.fields.path, mockProcessor).WithTrace().WithAuth(mockAuth).WithMiddlewares(mw).WithRateLimiting(5, 50)
-			if !tt.fields.missingMethod {
-				rb.MethodGet()
-			}
-			got, err := rb.Build()
-
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-				assert.Equal(t, Route{}, got)
-			} else {
-				assert.NoError(t, err)
-				assert.NotNil(t, got)
-			}
-		})
-	}
-}
+	handler := func(http.ResponseWriter, *http.Request) {}
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
 
-func TestNewRawRouteBuilder(t *testing.T) {
-	t.Parallel()
-	mockHandler := func(http.ResponseWriter, *http.Request) {}
 	type args struct {
-		path    string
-		handler http.HandlerFunc
-	}
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success":         {args: args{path: "/", handler: mockHandler}},
-		"invalid path":    {args: args{path: "", handler: mockHandler}, expectedErr: "path is empty"},
-		"invalid handler": {args: args{path: "/", handler: nil}, expectedErr: "handler is nil"},
+		method      string
+		path        string
+		handler     http.HandlerFunc
+		optionFuncs []RouteOptionFunc
 	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			rb := NewRawRouteBuilder(tt.args.path, tt.args.handler)
 
-			if tt.expectedErr != "" {
-				assert.Len(t, rb.errors, 1)
-				assert.EqualError(t, rb.errors[0], tt.expectedErr)
-			} else {
-				assert.Len(t, rb.errors, 0)
-			}
-		})
-	}
-}
-
-func TestNewRouteBuilder(t *testing.T) {
-	t.Parallel()
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	type args struct {
-		path      string
-		processor ProcessorFunc
-	}
 	tests := map[string]struct {
 		args        args
 		expectedErr string
 	}{
-		"success":         {args: args{path: "/", processor: mockProcessor}},
-		"invalid path":    {args: args{path: "", processor: mockProcessor}, expectedErr: "path is empty"},
-		"invalid handler": {args: args{path: "/", processor: nil}, expectedErr: "processor is nil"},
+		"success": {args: args{
+			method:      http.MethodGet,
+			path:        "/api",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{rateLimiting},
+		}},
+		"missing method": {args: args{
+			method:      "",
+			path:        "/api",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{rateLimiting},
+		}, expectedErr: "method is empty"},
+		"missing path": {args: args{
+			method:      http.MethodGet,
+			path:        "",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{rateLimiting},
+		}, expectedErr: "path is empty"},
+		"missing handler": {args: args{
+			method:      http.MethodGet,
+			path:        "/api",
+			handler:     nil,
+			optionFuncs: []RouteOptionFunc{rateLimiting},
+		}, expectedErr: "handler is nil"},
+		"missing middlewares": {args: args{
+			method:      http.MethodGet,
+			path:        "/api",
+			handler:     handler,
+			optionFuncs: []RouteOptionFunc{WithMiddlewares()},
+		}, expectedErr: "middlewares are empty"},
 	}
 	for name, tt := range tests {
 		tt := tt
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
-			rb := NewRouteBuilder(tt.args.path, tt.args.processor)
-
+			got, err := NewRoute(tt.args.method, tt.args.path, tt.args.handler, tt.args.optionFuncs...)
 			if tt.expectedErr != "" {
-				assert.Len(t, rb.errors, 1)
-				assert.EqualError(t, rb.errors[0], tt.expectedErr)
+				assert.EqualError(t, err, tt.expectedErr)
+				assert.Nil(t, got)
 			} else {
-				assert.Len(t, rb.errors, 0)
+				assert.NoError(t, err)
+				assertRoute(t, tt.args.method, tt.args.path, got)
+				assert.Equal(t, "GET /api", got.String())
 			}
 		})
 	}
 }
 
-func TestNewFileserver(t *testing.T) {
-	t.Parallel()
-	type args struct {
-		path         string
-		assetsDir    string
-		fallbackPath string
-	}
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success":                     {args: args{path: "/", assetsDir: "testdata", fallbackPath: "testdata/index.html"}},
-		"invalid path":                {args: args{path: "", assetsDir: "testdata", fallbackPath: "testdata/index.html"}, expectedErr: "path is empty"},
-		"invalid assets path":         {args: args{path: "/", assetsDir: "", fallbackPath: "testdata/index.html"}, expectedErr: "assets path is empty"},
-		"invalid fallback path":       {args: args{path: "/", assetsDir: "testdata", fallbackPath: ""}, expectedErr: "fallback path is empty"},
-		"assets path doesn't exist":   {args: args{path: "/", assetsDir: "", fallbackPath: "testdata/index.html"}, expectedErr: "assets path is empty"},
-		"fallback path doesn't exist": {args: args{path: "/", assetsDir: "testdata", fallbackPath: "testdata/missing.html"}, expectedErr: "fallback file [testdata/missing.html] doesn't exist"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			rb := NewFileServer(tt.args.path, tt.args.assetsDir, tt.args.fallbackPath)
-
-			if tt.expectedErr != "" {
-				assert.Len(t, rb.errors, 1)
-				assert.EqualError(t, rb.errors[0], tt.expectedErr)
-			} else {
-				assert.Len(t, rb.errors, 0)
-			}
-		})
-	}
+func TestNewGetRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewGetRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodGet, "/api", route)
 }
 
-func TestNewGetRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodGet, NewGetRouteBuilder("/", mockProcessor).method)
+func TestNewHeadRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewHeadRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodHead, "/api", route)
 }
 
-func TestNewHeadRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodHead, NewHeadRouteBuilder("/", mockProcessor).method)
+func TestNewPostRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewPostRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodPost, "/api", route)
 }
 
-func TestNewPostRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodPost, NewPostRouteBuilder("/", mockProcessor).method)
+func TestNewPutRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewPutRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodPut, "/api", route)
 }
 
-func TestNewPutGetRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodPut, NewPutRouteBuilder("/", mockProcessor).method)
+func TestNewPatchRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewPatchRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodPatch, "/api", route)
 }
 
-func TestNewPatchRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodPatch, NewPatchRouteBuilder("/", mockProcessor).method)
+func TestNewDeleteRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewDeleteRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodDelete, "/api", route)
 }
 
-func TestNewDeleteRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodDelete, NewDeleteRouteBuilder("/", mockProcessor).method)
+func TestNewConnectRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewConnectRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodConnect, "/api", route)
 }
 
-func TestNewConnectRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodConnect, NewConnectRouteBuilder("/", mockProcessor).method)
+func TestNewOptionsRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewOptionsRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodOptions, "/api", route)
 }
 
-func TestNewOptionsRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodOptions, NewOptionsRouteBuilder("/", mockProcessor).method)
+func TestNewTraceRoute(t *testing.T) {
+	rateLimiting, err := WithRateLimiting(1, 1)
+	require.NoError(t, err)
+	route, err := NewTraceRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
+		[]RouteOptionFunc{rateLimiting}...)
+	require.NoError(t, err)
+	assertRoute(t, http.MethodTrace, "/api", route)
 }
 
-func TestNewTraceRouteBuilder(t *testing.T) {
-	mockProcessor := func(context.Context, *Request) (*Response, error) { return nil, nil }
-	assert.Equal(t, http.MethodTrace, NewTraceRouteBuilder("/", mockProcessor).method)
+func assertRoute(t *testing.T, method, path string, route *Route) {
+	assert.Equal(t, method, route.Method())
+	assert.Equal(t, path, route.Path())
+	assert.NotNil(t, route.Handler())
+	assert.Len(t, route.Middlewares(), 1)
 }
 
-func TestRoutesBuilder_Build(t *testing.T) {
+func TestRoutes_Append(t *testing.T) {
 	t.Parallel()
-	mockHandler := func(http.ResponseWriter, *http.Request) {}
-	validRb := NewRawRouteBuilder("/", mockHandler).MethodGet()
-	invalidRb := NewRawRouteBuilder("/", mockHandler)
 	type args struct {
-		rbs []*RouteBuilder
+		route *Route
+		err   error
 	}
 	tests := map[string]struct {
 		args        args
 		expectedErr string
 	}{
-		"success": {
-			args: args{rbs: []*RouteBuilder{validRb}},
-		},
-		"invalid route builder": {
-			args:        args{rbs: []*RouteBuilder{invalidRb}},
-			expectedErr: "method is missing\n",
-		},
-		"duplicate routes": {
-			args:        args{rbs: []*RouteBuilder{validRb, validRb}},
-			expectedErr: "route with key get-/ is duplicate\n",
-		},
+		"success":      {args: args{route: &Route{}, err: nil}},
+		"error exist":  {args: args{route: &Route{}, err: errors.New("TEST")}, expectedErr: "TEST\n"},
+		"route is nil": {args: args{route: nil, err: nil}, expectedErr: "route is nil\n"},
 	}
 	for name, tt := range tests {
 		tt := tt
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
-			builder := NewRoutesBuilder()
-			for _, rb := range tt.args.rbs {
-				builder.Append(rb)
-			}
-			got, err := builder.Build()
-
+			r := &Routes{}
+			r.Append(tt.args.route, tt.args.err)
+			routes, err := r.Result()
 			if tt.expectedErr != "" {
 				assert.EqualError(t, err, tt.expectedErr)
-				assert.Nil(t, got)
+				assert.Empty(t, routes)
 			} else {
 				assert.NoError(t, err)
-				assert.Len(t, got, 1)
+				assert.NotEmpty(t, routes)
 			}
 		})
 	}
 }
-
-func TestRoute_Getters(t *testing.T) {
-	type testResponse struct {
-		Value string
-	}
-
-	path := "/foo"
-	expectedResponse := testResponse{"foo"}
-	r, err := NewRouteBuilder(path, testingHandlerMock(expectedResponse)).WithTrace().MethodPost().Build()
-	require.NoError(t, err)
-
-	assert.Equal(t, path, r.Path())
-	assert.Equal(t, http.MethodPost, r.Method())
-	assert.Len(t, r.Middlewares(), 2)
-
-	// the only way to test do we get the same handler that we provided initially, is to run it explicitly,
-	// since all we have in Route itself is a wrapper function
-	req, err := http.NewRequest(http.MethodPost, path, nil)
-	require.NoError(t, err)
-	wr := httptest.NewRecorder()
-
-	r.Handler().ServeHTTP(wr, req)
-	br, err := ioutil.ReadAll(wr.Body)
-	require.NoError(t, err)
-
-	gotResponse := testResponse{}
-	err = json.Unmarshal(br, &gotResponse)
-	require.NoError(t, err)
-
-	assert.Equal(t, expectedResponse, gotResponse)
-}
-
-func testingHandlerMock(expected interface{}) ProcessorFunc {
-	return func(_ context.Context, _ *Request) (*Response, error) {
-		return NewResponse(expected), nil
-	}
-}
diff --git a/component/http/v2/router/httprouter/router_option_test.go b/component/http/router/httprouter/router_option_test.go
similarity index 91%
rename from component/http/v2/router/httprouter/router_option_test.go
rename to component/http/router/httprouter/router_option_test.go
index 64312fe57..3ce981614 100644
--- a/component/http/v2/router/httprouter/router_option_test.go
+++ b/component/http/router/httprouter/router_option_test.go
@@ -4,21 +4,21 @@ import (
 	"net/http"
 	"testing"
 
+	patronhttp "github.com/beatlabs/patron/component/http"
 	"github.com/beatlabs/patron/component/http/middleware"
-	v2 "github.com/beatlabs/patron/component/http/v2"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestRoutes(t *testing.T) {
 	t.Parallel()
 	type args struct {
-		routes []*v2.Route
+		routes []*patronhttp.Route
 	}
 	tests := map[string]struct {
 		args        args
 		expectedErr string
 	}{
-		"success": {args: args{routes: []*v2.Route{{}, {}}}},
+		"success": {args: args{routes: []*patronhttp.Route{{}, {}}}},
 		"fail":    {args: args{routes: nil}, expectedErr: "routes are empty"},
 	}
 	for name, tt := range tests {
@@ -39,13 +39,13 @@ func TestRoutes(t *testing.T) {
 func TestAliveCheck(t *testing.T) {
 	t.Parallel()
 	type args struct {
-		acf v2.LivenessCheckFunc
+		acf patronhttp.LivenessCheckFunc
 	}
 	tests := map[string]struct {
 		args        args
 		expectedErr string
 	}{
-		"success": {args: args{acf: func() v2.AliveStatus { return v2.Alive }}},
+		"success": {args: args{acf: func() patronhttp.AliveStatus { return patronhttp.Alive }}},
 		"fail":    {args: args{acf: nil}, expectedErr: "alive check function is nil"},
 	}
 	for name, tt := range tests {
@@ -66,13 +66,13 @@ func TestAliveCheck(t *testing.T) {
 func TestReadyCheck(t *testing.T) {
 	t.Parallel()
 	type args struct {
-		rcf v2.ReadyCheckFunc
+		rcf patronhttp.ReadyCheckFunc
 	}
 	tests := map[string]struct {
 		args        args
 		expectedErr string
 	}{
-		"success": {args: args{rcf: func() v2.ReadyStatus { return v2.Ready }}},
+		"success": {args: args{rcf: func() patronhttp.ReadyStatus { return patronhttp.Ready }}},
 		"fail":    {args: args{rcf: nil}, expectedErr: "ready check function is nil"},
 	}
 	for name, tt := range tests {
diff --git a/component/http/v2/router/httprouter/router_test.go b/component/http/router/httprouter/router_test.go
similarity index 88%
rename from component/http/v2/router/httprouter/router_test.go
rename to component/http/router/httprouter/router_test.go
index c238c08fa..161cc88a2 100644
--- a/component/http/v2/router/httprouter/router_test.go
+++ b/component/http/router/httprouter/router_test.go
@@ -5,14 +5,14 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	v2 "github.com/beatlabs/patron/component/http/v2"
+	patronhttp "github.com/beatlabs/patron/component/http"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestNew(t *testing.T) {
 	t.Parallel()
-	route, err := v2.NewRoute(http.MethodGet, "/api/", func(writer http.ResponseWriter, request *http.Request) {
+	route, err := patronhttp.NewRoute(http.MethodGet, "/api/", func(writer http.ResponseWriter, request *http.Request) {
 		writer.WriteHeader(200)
 	})
 	require.NoError(t, err)
@@ -41,7 +41,7 @@ func TestNew(t *testing.T) {
 }
 
 func TestVerifyRouter(t *testing.T) {
-	route, err := v2.NewRoute(http.MethodGet, "/api/", func(writer http.ResponseWriter, request *http.Request) {
+	route, err := patronhttp.NewRoute(http.MethodGet, "/api/", func(writer http.ResponseWriter, request *http.Request) {
 		writer.WriteHeader(200)
 	})
 	require.NoError(t, err)
diff --git a/component/http/v2/component_test.go b/component/http/v2/component_test.go
deleted file mode 100644
index d2ed420bc..000000000
--- a/component/http/v2/component_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package v2
-
-import (
-	"context"
-	"fmt"
-	"net"
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-type stubHandler struct{}
-
-func (s stubHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) {
-	rw.WriteHeader(200)
-}
-
-func TestNew(t *testing.T) {
-	hnd := &stubHandler{}
-	type args struct {
-		handler      http.Handler
-		oo           []OptionFunc
-		port         string
-		readTimeout  string
-		writeTimeout string
-	}
-	tests := map[string]struct {
-		args        args
-		expected    *Component
-		expectedErr string
-	}{
-		"success": {
-			args: args{handler: hnd},
-			expected: &Component{
-				port:                defaultPort,
-				readTimeout:         defaultReadTimeout,
-				writeTimeout:        defaultWriteTimeout,
-				shutdownGracePeriod: defaultShutdownGracePeriod,
-				handlerTimeout:      defaultHandlerTimeout,
-				handler:             hnd,
-			},
-		},
-		"success, env vars": {
-			args: args{
-				handler:      hnd,
-				port:         "8080",
-				readTimeout:  "10s",
-				writeTimeout: "11s",
-			},
-			expected: &Component{
-				port:                8080,
-				readTimeout:         10 * time.Second,
-				writeTimeout:        11 * time.Second,
-				shutdownGracePeriod: defaultShutdownGracePeriod,
-				handlerTimeout:      defaultHandlerTimeout,
-				handler:             hnd,
-			},
-		},
-		"failure, port env vars": {
-			args: args{
-				handler: hnd,
-				port:    "aaa",
-			},
-			expectedErr: `env var for HTTP default port is not valid: strconv.ParseInt: parsing "aaa": invalid syntax`,
-		},
-		"failure, read timeout env vars": {
-			args: args{
-				handler:     hnd,
-				readTimeout: "aaa",
-			},
-			expectedErr: `env var for HTTP read timeout is not valid: time: invalid duration "aaa"`,
-		},
-		"failure, write timeout env vars": {
-			args: args{
-				handler:      hnd,
-				writeTimeout: "aaa",
-			},
-			expectedErr: `env var for HTTP write timeout is not valid: time: invalid duration "aaa"`,
-		},
-		"missing handler": {
-			args:        args{handler: nil},
-			expectedErr: "handler is nil",
-		},
-		"option error": {
-			args:        args{handler: &stubHandler{}, oo: []OptionFunc{WithPort(500000)}},
-			expectedErr: "invalid HTTP Port provided",
-		},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			if tt.args.port != "" {
-				t.Setenv("PATRON_HTTP_DEFAULT_PORT", tt.args.port)
-			}
-
-			if tt.args.readTimeout != "" {
-				t.Setenv("PATRON_HTTP_READ_TIMEOUT", tt.args.readTimeout)
-			}
-
-			if tt.args.writeTimeout != "" {
-				t.Setenv("PATRON_HTTP_WRITE_TIMEOUT", tt.args.writeTimeout)
-			}
-
-			got, err := New(tt.args.handler, tt.args.oo...)
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-				assert.Nil(t, got)
-			} else {
-				assert.NoError(t, err)
-				assert.Equal(t, tt.expected, got)
-			}
-		})
-	}
-}
-
-func TestComponent_ListenAndServe_DefaultRoutes_Shutdown(t *testing.T) {
-	listener, err := net.Listen("tcp", ":0") //nolint:gosec
-	require.NoError(t, err)
-	port, ok := listener.Addr().(*net.TCPAddr)
-	assert.True(t, ok)
-	require.NoError(t, listener.Close())
-
-	cmp, err := New(&stubHandler{}, WithPort(port.Port))
-	assert.NoError(t, err)
-	done := make(chan bool)
-	ctx, cnl := context.WithCancel(context.Background())
-	go func() {
-		assert.NoError(t, cmp.Run(ctx))
-		done <- true
-	}()
-	time.Sleep(10 * time.Millisecond)
-	rsp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port.Port))
-	assert.NoError(t, err)
-	assert.Equal(t, http.StatusOK, rsp.StatusCode)
-	cnl()
-	assert.True(t, <-done)
-}
diff --git a/component/http/v2/encoding/json/json_test.go b/component/http/v2/encoding/json/json_test.go
deleted file mode 100644
index 85aaa93c4..000000000
--- a/component/http/v2/encoding/json/json_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Package json contains helper methods to handler requests and responses more easily.
-package json
-
-import (
-	"bytes"
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/beatlabs/patron/encoding"
-	"github.com/beatlabs/patron/encoding/json"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-type customer struct {
-	Name string
-}
-
-func TestReadRequest(t *testing.T) {
-	t.Parallel()
-
-	expected := customer{Name: "John Wick"}
-	buf, err := json.Encode(expected)
-	require.NoError(t, err)
-
-	type args struct {
-		header string
-		body   []byte
-	}
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success, no header":           {args: args{header: "", body: buf}},
-		"success, single star header":  {args: args{header: "*", body: buf}},
-		"success, double star header":  {args: args{header: "*/*", body: buf}},
-		"success, json header":         {args: args{header: json.Type, body: buf}},
-		"success, json charset header": {args: args{header: json.TypeCharset, body: buf}},
-		"failure, invalid header":      {args: args{header: "text/xml", body: buf}, expectedErr: "invalid content type provided: text/xml"},
-		"failure, invalid body":        {args: args{header: "", body: []byte("-")}, expectedErr: "unexpected EOF"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-
-			var got customer
-
-			req := createRequest(t, tt.args.body, tt.args.header)
-
-			err := ReadRequest(req, &got)
-
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-			} else {
-				assert.NoError(t, err)
-				assert.Equal(t, expected, got)
-			}
-		})
-	}
-}
-
-func createRequest(t *testing.T, buf []byte, header string) *http.Request {
-	req, err := http.NewRequest(http.MethodPost, "/api/customer", bytes.NewBuffer(buf))
-	require.NoError(t, err)
-
-	if header == "" {
-		return req
-	}
-
-	req.Header.Set(encoding.ContentTypeHeader, header)
-
-	return req
-}
-
-func TestWriteResponse(t *testing.T) {
-	t.Parallel()
-
-	expected := customer{Name: "John Wick"}
-	expectedBuf, err := json.Encode(expected)
-	require.NoError(t, err)
-
-	rsp := httptest.NewRecorder()
-	err = WriteResponse(rsp, http.StatusOK, expected)
-	assert.NoError(t, err)
-	assert.Equal(t, http.StatusOK, rsp.Code)
-	assert.Equal(t, expectedBuf, rsp.Body.Bytes())
-	assert.Equal(t, json.Type, rsp.Header().Get(encoding.ContentTypeHeader))
-}
-
-func TestValidateAcceptHeader(t *testing.T) {
-	t.Parallel()
-
-	expected := customer{Name: "John Wick"}
-
-	type args struct {
-		acceptHeader *string
-		status       int
-		payload      interface{}
-	}
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success, missing header":      {args: args{acceptHeader: nil, status: http.StatusOK, payload: expected}},
-		"success, empty header":        {args: args{acceptHeader: stringPointer(""), status: http.StatusOK, payload: expected}},
-		"success, single star header":  {args: args{acceptHeader: stringPointer(singleStar), status: http.StatusOK, payload: expected}},
-		"success, double star header":  {args: args{acceptHeader: stringPointer(doubleStar), status: http.StatusOK, payload: expected}},
-		"success, identity header":     {args: args{acceptHeader: stringPointer("identity"), status: http.StatusOK, payload: expected}},
-		"success, json header":         {args: args{acceptHeader: stringPointer(json.Type), status: http.StatusOK, payload: expected}},
-		"success, json charset header": {args: args{acceptHeader: stringPointer(json.TypeCharset), status: http.StatusOK, payload: expected}},
-		"success, multi header":        {args: args{acceptHeader: stringPointer(fmt.Sprintf("%s,%s", singleStar, json.Type)), status: http.StatusOK, payload: expected}},
-		"failure, invalid header":      {args: args{acceptHeader: stringPointer("-"), status: http.StatusOK, payload: expected}, expectedErr: "invalid accept header: -"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-
-			req, err := http.NewRequest(http.MethodPost, "/api/customer", nil)
-			require.NoError(t, err)
-			if tt.args.acceptHeader != nil {
-				req.Header.Set(encoding.AcceptHeader, *tt.args.acceptHeader)
-			}
-
-			err = ValidateAcceptHeader(req)
-
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-			} else {
-				assert.NoError(t, err)
-			}
-		})
-	}
-}
-
-func stringPointer(val string) *string {
-	return &val
-}
diff --git a/component/http/v2/route_test.go b/component/http/v2/route_test.go
deleted file mode 100644
index 7e307a3f0..000000000
--- a/component/http/v2/route_test.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package v2
-
-import (
-	"errors"
-	"net/http"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func TestNewRoute(t *testing.T) {
-	t.Parallel()
-
-	handler := func(http.ResponseWriter, *http.Request) {}
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-
-	type args struct {
-		method      string
-		path        string
-		handler     http.HandlerFunc
-		optionFuncs []RouteOptionFunc
-	}
-
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success": {args: args{
-			method:      http.MethodGet,
-			path:        "/api",
-			handler:     handler,
-			optionFuncs: []RouteOptionFunc{rateLimiting},
-		}},
-		"missing method": {args: args{
-			method:      "",
-			path:        "/api",
-			handler:     handler,
-			optionFuncs: []RouteOptionFunc{rateLimiting},
-		}, expectedErr: "method is empty"},
-		"missing path": {args: args{
-			method:      http.MethodGet,
-			path:        "",
-			handler:     handler,
-			optionFuncs: []RouteOptionFunc{rateLimiting},
-		}, expectedErr: "path is empty"},
-		"missing handler": {args: args{
-			method:      http.MethodGet,
-			path:        "/api",
-			handler:     nil,
-			optionFuncs: []RouteOptionFunc{rateLimiting},
-		}, expectedErr: "handler is nil"},
-		"missing middlewares": {args: args{
-			method:      http.MethodGet,
-			path:        "/api",
-			handler:     handler,
-			optionFuncs: []RouteOptionFunc{WithMiddlewares()},
-		}, expectedErr: "middlewares are empty"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			got, err := NewRoute(tt.args.method, tt.args.path, tt.args.handler, tt.args.optionFuncs...)
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-				assert.Nil(t, got)
-			} else {
-				assert.NoError(t, err)
-				assertRoute(t, tt.args.method, tt.args.path, got)
-				assert.Equal(t, "GET /api", got.String())
-			}
-		})
-	}
-}
-
-func TestNewGetRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewGetRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodGet, "/api", route)
-}
-
-func TestNewHeadRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewHeadRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodHead, "/api", route)
-}
-
-func TestNewPostRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewPostRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodPost, "/api", route)
-}
-
-func TestNewPutRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewPutRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodPut, "/api", route)
-}
-
-func TestNewPatchRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewPatchRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodPatch, "/api", route)
-}
-
-func TestNewDeleteRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewDeleteRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodDelete, "/api", route)
-}
-
-func TestNewConnectRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewConnectRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodConnect, "/api", route)
-}
-
-func TestNewOptionsRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewOptionsRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodOptions, "/api", route)
-}
-
-func TestNewTraceRoute(t *testing.T) {
-	rateLimiting, err := WithRateLimiting(1, 1)
-	require.NoError(t, err)
-	route, err := NewTraceRoute("/api", func(writer http.ResponseWriter, request *http.Request) {},
-		[]RouteOptionFunc{rateLimiting}...)
-	require.NoError(t, err)
-	assertRoute(t, http.MethodTrace, "/api", route)
-}
-
-func assertRoute(t *testing.T, method, path string, route *Route) {
-	assert.Equal(t, method, route.Method())
-	assert.Equal(t, path, route.Path())
-	assert.NotNil(t, route.Handler())
-	assert.Len(t, route.Middlewares(), 1)
-}
-
-func TestRoutes_Append(t *testing.T) {
-	t.Parallel()
-	type args struct {
-		route *Route
-		err   error
-	}
-	tests := map[string]struct {
-		args        args
-		expectedErr string
-	}{
-		"success":      {args: args{route: &Route{}, err: nil}},
-		"error exist":  {args: args{route: &Route{}, err: errors.New("TEST")}, expectedErr: "TEST\n"},
-		"route is nil": {args: args{route: nil, err: nil}, expectedErr: "route is nil\n"},
-	}
-	for name, tt := range tests {
-		tt := tt
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			r := &Routes{}
-			r.Append(tt.args.route, tt.args.err)
-			routes, err := r.Result()
-			if tt.expectedErr != "" {
-				assert.EqualError(t, err, tt.expectedErr)
-				assert.Empty(t, routes)
-			} else {
-				assert.NoError(t, err)
-				assert.NotEmpty(t, routes)
-			}
-		})
-	}
-}