Full Code of luraproject/lura for AI

master 6d79b4ef723b cached
158 files
684.6 KB
195.2k tokens
1134 symbols
1 requests
Download .txt
Showing preview only (726K chars total). Download the full file or copy to clipboard to get everything.
Repository: luraproject/lura
Branch: master
Commit: 6d79b4ef723b
Files: 158
Total size: 684.6 KB

Directory structure:
gitextract_fat13st9/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── feature_request.md
│   │   └── report-vulnerability.md
│   ├── label-commenter-config.yml
│   └── workflows/
│       ├── go.yml
│       ├── labels.yml
│       └── lock-threads.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── async/
│   ├── asyncagent.go
│   └── asyncagent_test.go
├── backoff/
│   ├── backoff.go
│   └── backoff_test.go
├── config/
│   ├── config.go
│   ├── config_test.go
│   ├── parser.go
│   ├── parser_test.go
│   ├── uri.go
│   └── uri_test.go
├── core/
│   └── version.go
├── docs/
│   ├── BENCHMARKS.md
│   ├── CONFIG.md
│   ├── OVERVIEW.md
│   └── README.md
├── encoding/
│   ├── encoding.go
│   ├── encoding_test.go
│   ├── json_benchmark_test.go
│   ├── json_test.go
│   └── register.go
├── go.mod
├── go.sum
├── logging/
│   ├── log.go
│   └── log_test.go
├── plugin/
│   ├── plugin.go
│   └── plugin_test.go
├── proxy/
│   ├── balancing.go
│   ├── balancing_benchmark_test.go
│   ├── balancing_test.go
│   ├── concurrent.go
│   ├── concurrent_benchmark_test.go
│   ├── concurrent_test.go
│   ├── factory.go
│   ├── factory_test.go
│   ├── formatter.go
│   ├── formatter_benchmark_test.go
│   ├── formatter_test.go
│   ├── graphql.go
│   ├── graphql_test.go
│   ├── headers_filter.go
│   ├── headers_filter_test.go
│   ├── http.go
│   ├── http_benchmark_test.go
│   ├── http_response.go
│   ├── http_response_test.go
│   ├── http_test.go
│   ├── logging.go
│   ├── logging_test.go
│   ├── merging.go
│   ├── merging_benchmark_test.go
│   ├── merging_test.go
│   ├── plugin/
│   │   ├── modifier.go
│   │   ├── modifier_test.go
│   │   └── tests/
│   │       ├── error/
│   │       │   └── main.go
│   │       └── logger/
│   │           └── main.go
│   ├── plugin.go
│   ├── plugin_test.go
│   ├── proxy.go
│   ├── proxy_test.go
│   ├── query_strings_filter.go
│   ├── query_strings_filter_test.go
│   ├── register.go
│   ├── register_test.go
│   ├── request.go
│   ├── request_benchmark_test.go
│   ├── request_test.go
│   ├── shadow.go
│   ├── shadow_test.go
│   ├── stack_benchmark_test.go
│   ├── stack_test.go
│   ├── static.go
│   └── static_test.go
├── register/
│   ├── register.go
│   └── register_test.go
├── router/
│   ├── chi/
│   │   ├── endpoint.go
│   │   ├── endpoint_benchmark_test.go
│   │   ├── endpoint_test.go
│   │   ├── router.go
│   │   └── router_test.go
│   ├── gin/
│   │   ├── debug.go
│   │   ├── debug_test.go
│   │   ├── echo.go
│   │   ├── echo_test.go
│   │   ├── endpoint.go
│   │   ├── endpoint_benchmark_test.go
│   │   ├── endpoint_test.go
│   │   ├── engine.go
│   │   ├── engine_test.go
│   │   ├── render.go
│   │   ├── render_test.go
│   │   ├── router.go
│   │   ├── router_test.go
│   │   └── safecast.go
│   ├── gorilla/
│   │   ├── router.go
│   │   └── router_test.go
│   ├── helper.go
│   ├── helper_test.go
│   ├── httptreemux/
│   │   ├── router.go
│   │   └── router_test.go
│   ├── mux/
│   │   ├── debug.go
│   │   ├── debug_test.go
│   │   ├── echo.go
│   │   ├── echo_test.go
│   │   ├── endpoint.go
│   │   ├── endpoint_benchmark_test.go
│   │   ├── endpoint_test.go
│   │   ├── engine.go
│   │   ├── engine_test.go
│   │   ├── render.go
│   │   ├── render_test.go
│   │   ├── router.go
│   │   └── router_test.go
│   ├── negroni/
│   │   ├── router.go
│   │   └── router_test.go
│   └── router.go
├── sd/
│   ├── dnssrv/
│   │   ├── subscriber.go
│   │   └── subscriber_test.go
│   ├── loadbalancing.go
│   ├── loadbalancing_benchmark_test.go
│   ├── loadbalancing_test.go
│   ├── register.go
│   ├── register_test.go
│   └── subscriber.go
├── test/
│   ├── doc.go
│   └── integration_test.go
└── transport/
    └── http/
        ├── client/
        │   ├── executor.go
        │   ├── executor_test.go
        │   ├── graphql/
        │   │   ├── graphql.go
        │   │   └── graphql_test.go
        │   ├── plugin/
        │   │   ├── doc.go
        │   │   ├── executor.go
        │   │   ├── plugin.go
        │   │   ├── plugin_test.go
        │   │   └── tests/
        │   │       └── main.go
        │   ├── status.go
        │   └── status_test.go
        └── server/
            ├── plugin/
            │   ├── doc.go
            │   ├── plugin.go
            │   ├── plugin_test.go
            │   ├── server.go
            │   └── tests/
            │       └── main.go
            ├── server.go
            ├── server_test.go
            └── tls_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Configuration used
2. Steps to run the software

**Expected behavior**
A clear and concise description of what you expected to happen.

**Logs**
If applicable, any logs and debugging information

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/ISSUE_TEMPLATE/report-vulnerability.md
================================================
---
name: Report vulnerability
about: Report a vulnerability
title: ''
labels: ''
assignees: ''

---

For **private vulnerabilities** write to support@devops.faith instead

**Vulnerabilty description**
Explain the vulnerability in detail and how to reproduce when possible

**Reference**
E.g: https://nvd.nist.gov/vuln/detail/CVE-2019-6486

**Additional information**
Impact, Known Affected Software Configurations, etc.


================================================
FILE: .github/label-commenter-config.yml
================================================
comment:
  footer:  |
    ---
    > This is an automated comment. Responding to the bot or mentioning it won't have any effect

labels:
  - name: invalid
    labeled:
      issue:
        body: |
          Hi, thanks for bringing this issue to our attention.

          Unfortunately, this issue has been marked invalid and will be closed.
          The most common reasons for marking an issue or pull request as invalid is because:

          - It's vague or not clearly actionable
          - It contains insufficient details to reproduce
          - It's plugin review or custom code review
          - It does not use the issue template
          - It's unrelated to the project (e.g., related to one of its libraries)
          - It does not follow the technical philosophy of the project
          - Violates our [Code of Conduct](https://lfprojects.org/policies/code-of-conduct/)
          - It's about KrakenD functionalities (which uses and mantain Lura, but is not part of the Linux Foundation)

          You can still make an edit or leave additional comments that lead to reopening this issue.
        action: close
      pr:
        body: |
          Hi @{{ pull_request.user.login }}, thanks for having spent the time to code and send an improvement to Lura.

          Unfortunately, this pull request has been marked as invalid and will be closed without merging.
          The most common reasons for marking an issue or pull request as invalid is because:

          - Contains insufficient details, it's unclear for the reviewer, or it's impossible to move forward without a lot of interaction
          - It's unrelated to the project (e.g., related to one of its libraries)
          - It does not follow the philosophy of the project
          - Violates our [Code of Conduct](https://lfprojects.org/policies/code-of-conduct/)

          You can still make an edit or leave additional comments that lead to reopening this issue.
        action: close
  - name: wontfix
    labeled:
      issue:
        body: |
          Hi, thank you for bringing this issue to our attention.

          Many factors influence our product roadmaps and determine the features, fixes, and suggestions we implement.
          When deciding what to prioritize and work on, we combine your feedback and suggestions with insights from our development team, product analytics, research findings, and more.

          This information, combined with our product vision, determines what we implement and its priority order. Unfortunately, we don't foresee this issue progressing any further in the short-medium term, and we are closing it.

          While this issue is now closed, we continue monitoring requests for our future roadmap, **including this one**.

          If you have additional information you would like to provide, please share.
        action: close
      pr:
        body: |
          Hi @{{ pull_request.user.login }}, thanks for having spent the time to code and send an improvement to Lura.

          When deciding what to accept and include in our product, we are cautious about what we add, and the time our team needs to spend to have it done exemplary, considering all edge cases. As a result, we rarely add features, make changes that a tiny number of users need, or are out-of-scope of the project. For example, we might choose safety over having a specific additional feature that adds complexity we don't see crystal clear. Sometimes "less is more" because we can focus better on crucial functionality.

          Although it's never nice to reject someone's work, after evaluating your code, we think it's better not to merge it or continue putting effort into it on both sides. If this PR is to solve what you considered a bug, our understanding of the functionality does not need to match your thinking. So while this pull request is now closed, **this is not a definitive decision**. You are free to provide additional information that would help us change our minds.

          Lura is indirectly used in millions of servers every year, and the slightest change has an impact. We are doing it for all community users' benefit and to keep the code's simplicity, philosophy, and maintainability for the long run.
        action: close
  - name: duplicate
    labeled:
      issue:
        body: An issue like this already exists, please follow it in the other thread
        action: close
  - name: good first issue
    labeled:
      issue:
        body: This issue is easy for contributing. Everyone can work on this.
  - name: spam
    labeled:
      issue:
        body: |
          This issue has been **LOCKED** because of spam!

          Please do not spam messages and/or issues on the issue tracker. You may get blocked from this repository for doing so.
        action: close
        locking: lock
        lock_reason: spam
      pr:
        body: |
          This pull-request has been **LOCKED** because of spam!

          Please do not spam messages and/or pull-requests on this project. You may get blocked from this repository for doing so.
        action: close
        locking: lock
        lock_reason: spam

================================================
FILE: .github/workflows/go.yml
================================================
name: Go

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: "1.25"

    - name: Build
      run: go build -v ./...

    - name: Test
      run: go test -cover -race ./...

    - name: Integration Test
      run: go test -tags integration ./test

================================================
FILE: .github/workflows/labels.yml
================================================
name: Label commenter
on:
  issues:
    types: [labeled, unlabeled]
  pull_request_target:
    types: [labeled, unlabeled]
jobs:
  stale:
    uses: luraproject/.github/.github/workflows/label-commenter.yml@main


================================================
FILE: .github/workflows/lock-threads.yml
================================================
name: 'Lock Threads'

on:
  schedule:
    - cron: '0 0 * * *'
  workflow_dispatch:

permissions:
  issues: write
  pull-requests: write

concurrency:
  group: lock

jobs:
  action:
    runs-on: ubuntu-latest
    steps:
      - uses: dessant/lock-threads@v3
        with:
          pr-inactive-days: '90'
          issue-inactive-days: '90'
          add-issue-labels: 'locked'
          issue-comment: >
            This issue was marked as resolved a long time ago and now has been
            automatically locked as there has not been any recent activity after it.
            You can still open a new issue and reference this link.
          pr-comment: >
            This pull request was marked as resolved a long time ago and now has been
            automatically locked as there has not been any recent activity after it.
            You can still open a new issue and reference this link.

================================================
FILE: .gitignore
================================================
vendor
server.rsa.crt
server.rsa.key
*.pem
*.json
*.toml
*.dot
*.out
*.so
bench_res
.cover
.idea

*DS_Store


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@krakend.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Thank you for your interest in contributing to Lura, there are several ways
you can contribute and make this project more awesome, please see below:

## Reporting an Issue

If you believe you have found an issue with the code please do not hesitate to file an issue in [Github](https://github.com/luraproject/lura/issues). When
filing the issue please describe the problem with the maximum level of detail
and the steps to reproduce the problem, including information about your
environment.

You can also open an issue requesting for help or doing a question and it's
also a good way of contributing since other users might be in a similar
position.

Please note we have a code of conduct, please follow it in all your interactions with the project.

## Code Contributions

When contributing to this repository, it is generally a good idea to discuss
the change with the owners before investing a lot of time coding. The process
could be:

1. Open an issue explaining the improvment or fix you want to add
2. [Fork the project](https://github.com/luraproject/lura/fork)
3. Code it in your fork
4. Submit a [pull request](https://help.github.com/articles/creating-a-pull-request) referencing the issue


Your work will then be reviewed as soon as possible (suggestions about some
changes, improvements or alternatives may be given).

**Don't forget to add tests**, make sure that they all pass!

# Help with Git

Once the repository is forked, you should track the upstream (original) one
using the following command:

    git remote add upstream https://github.com/luraproject/lura.git

Then you should create your own branch:

    git checkout -b <prefix>/<micro-title>-<issue-number>

Once your changes are done (`git commit -am '<descriptive-message>'`), get the
upstream changes:

    git checkout master
    git pull --rebase origin master
    git pull --rebase upstream master
    git checkout <your-branch>
    git rebase master

Finally, publish your changes:

    git push -f origin <your-branch>

You should be now ready to make a pull request.


================================================
FILE: LICENSE
================================================
Copyright © 2021 Lura Project a Series of LF Projects, LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

================================================
FILE: Makefile
================================================
.PHONY: all test build benchmark

OS := $(shell uname | tr '[:upper:]' '[:lower:]')
GIT_COMMIT := $(shell git rev-parse --short=7 HEAD)

all: test build

generate:
	go generate ./...
	go build -buildmode=plugin -o ./transport/http/client/plugin/tests/lura-client-example.so ./transport/http/client/plugin/tests
	go build -buildmode=plugin -o ./transport/http/server/plugin/tests/lura-server-example.so ./transport/http/server/plugin/tests
	go build -buildmode=plugin -o ./proxy/plugin/tests/lura-request-modifier-example.so ./proxy/plugin/tests/logger
	go build -buildmode=plugin -o ./proxy/plugin/tests/lura-error-example.so ./proxy/plugin/tests/error

test: generate
	go test -cover -race ./...
	go test -tags integration ./test/...
	go test -tags integration ./transport/...
	go test -tags integration ./proxy/...

benchmark:
	@mkdir -p bench_res
	@touch bench_res/${GIT_COMMIT}.out
	@go test -run none -bench . -benchmem ./... >> bench_res/${GIT_COMMIT}.out

build:
	go build ./...


================================================
FILE: README.md
================================================
<img src="https://luraproject.org/images/lura-logo-header.svg" width="300" />

# The Lura Project framework

[![Go Report Card](https://goreportcard.com/badge/github.com/luraproject/lura/v2)](https://goreportcard.com/report/github.com/luraproject/lura/v2)
[![GoDoc](https://godoc.org/github.com/luraproject/lura/v2?status.svg)](https://godoc.org/github.com/luraproject/lura/v2)
![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3151/badge)
[![Slack Widget](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=red)](https://gophers.slack.com/messages/lura)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fluraproject%2Flura.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fluraproject%2Flura%2Fv2?ref=badge_shield&issueType=license)


An open framework to assemble ultra performance API Gateways with middlewares; formerly known as _KrakenD framework_, and core service of the [KrakenD API Gateway](http://www.krakend.io).

## Motivation

Consumers of REST API content (specially in microservices) often query backend services that weren't coded for the UI implementation. This is of course a good practice, but the UI consumers need to do implementations that suffer a lot of complexity and burden with the sizes of their microservices responses.

Lura is an **API Gateway** builder and proxy generator that sits between the client and all the source servers, adding a new layer that removes all the complexity to the clients, providing them only the information that the UI needs. Lura acts as an **aggregator** of many sources into single endpoints and allows you to group, wrap, transform and shrink responses. Additionally it supports a myriad of middlewares and plugins that allow you to extend the functionality, such as adding Oauth authorization or security layers.

Lura not only supports HTTP(S), but because it is a set of generic libraries you can build all type of API Gateways and proxies, including for instance, an RPC gateway.

### Practical Example

A mobile developer needs to construct a single front page that requires data from 4 different calls to their backend services, e.g:

    1) api.store.server/products
    2) api.store.server/marketing-promos
    3) api.users.server/users/{id_user}
    4) api.users.server/shopping-cart/{id_user}

The screen is very simple, and the mobile client _only_ needs to retrieve data from 4 different sources, wait for the round trip and then hand pick only a few fields from the response.

What if the mobile could call a single endpoint?

    1) lura.server/frontpage/{id_user}

That's something Lura can do for you. And this is how it would look like:

![Gateway](https://luraproject.org/images/docs/lura-gateway.png)

Lura would merge all the data and return only the fields you need (the difference in size in the graph).

Visit the [Lura Project website](https://luraproject.org) for more information.

## What's in this repository?

The source code for the [Lura project](https://luraproject.org) framework. It is designed to work with your own middleware and extend the functionality by using small, independent, reusable components following the Unix philosophy.

Use this repository if you want to **build from source your API Gateway** or if you want to **reuse the components in another application**.

If you need a fully functional API Gateway you can [download the KrakenD binary for your architecture](http://www.krakend.io/download) or [build it yourself](https://github.com/krakend/krakend-ce).


## Library Usage
The Lura project is presented as a **Go library** that you can include in your own Go application to build a powerful proxy or API gateway. For a complete example, check the [KrakenD CE repository](https://github.com/krakend/krakend-ce).

Of course, you will need [Go installed](https://golang.org/doc/install) in your system to compile the code.

A ready to use example:

```go
    package main

    import (
        "flag"
        "log"
        "os"

        "github.com/luraproject/lura/config"
        "github.com/luraproject/lura/logging"
        "github.com/luraproject/lura/proxy"
        "github.com/luraproject/lura/router/gin"
    )

    func main() {
        port := flag.Int("p", 0, "Port of the service")
        logLevel := flag.String("l", "ERROR", "Logging level")
        debug := flag.Bool("d", false, "Enable the debug")
        configFile := flag.String("c", "/etc/lura/configuration.json", "Path to the configuration filename")
        flag.Parse()

        parser := config.NewParser()
        serviceConfig, err := parser.Parse(*configFile)
        if err != nil {
            log.Fatal("ERROR:", err.Error())
        }
        serviceConfig.Debug = serviceConfig.Debug || *debug
        if *port != 0 {
            serviceConfig.Port = *port
        }

        logger, _ := logging.NewLogger(*logLevel, os.Stdout, "[LURA]")

        routerFactory := gin.DefaultFactory(proxy.DefaultFactory(logger), logger)

        routerFactory.New().Run(serviceConfig)
    }
```

Visit the [framework overview](/docs/OVERVIEW.md) for more details about the components of the Lura project.

## Configuration file

[Lura config file](/docs/CONFIG.md)

## Benchmarks

Check out the [benchmark results](/docs/BENCHMARKS.md) of several Lura components

## Contributing
We are always happy to receive contributions. If you have questions, suggestions, bugs please open an issue.
If you want to submit the code, create the issue and send us a pull request for review.

Read [CONTRIBUTING.md](/CONTRIBUTING.md) for more information.


## Want more?
- Follow us on Twitter: [@luraproject](https://twitter.com/luraproject)
- Visit our [Slack channel](https://gophers.slack.com/messages/lura)
- **Read the [documentation](/docs/OVERVIEW.md)**

Enjoy Lura!


## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fluraproject%2Flura.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fluraproject%2Flura?ref=badge_large)


================================================
FILE: SECURITY.md
================================================
# Security Policy
Lura only fixes the latest version of the software, and does not patch prior versions.

## Reporting a Vulnerability

Please email security@krakend.io with your discovery. As soon as we read and understand your finding we will provide an answer with next steps and possible timelines.

We want to thank you in advance for the time you have spent to follow this issue, as it helps all open source users. We develop our software in the open with the help of a global community of developers and contributors with whom we share a common understanding and trust in the free exchange of knowledge.

The Lura Project DOES NOT provide cash awards for discovered vulnerabilities at this time.

Thank you




================================================
FILE: async/asyncagent.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
 */
package async

import (
	"context"
	"errors"
	"fmt"
	"math"

	"github.com/luraproject/lura/v2/backoff"
	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
	"github.com/luraproject/lura/v2/proxy"

	"golang.org/x/sync/errgroup"
)

// Options contains the configuration to pass to the async agent factory
type Options struct {
	// Agent keeps the configuration for the async agent
	Agent *config.AsyncAgent
	// Endpoint encapsulates the configuration for the associated pipe
	Endpoint *config.EndpointConfig
	// Proxy is the pipe associated with the async agent
	Proxy proxy.Proxy
	// AgentPing is the channel for the agent to send ping messages
	AgentPing chan<- string
	// G is the error group responsible for managing the agents and the router itself
	G *errgroup.Group
	// ShouldContinue is a function signaling when to stop the connection retries
	ShouldContinue func(int) bool
	// BackoffF is a function encapsulating the backoff strategy
	BackoffF backoff.TimeToWaitBeforeRetry
	Logger   logging.Logger
}

// Factory is a function able to start an async agent
type Factory func(context.Context, Options) bool

// AgentStarter groups a set of factories to be used
type AgentStarter []Factory

// Start executes all the factories for each async agent configuration
func (a AgentStarter) Start(
	ctx context.Context,
	agents []*config.AsyncAgent,
	logger logging.Logger,
	agentPing chan<- string,
	pf proxy.Factory,
) func() error {
	if len(a) == 0 {
		return func() error { return ErrNoAgents }
	}

	g, ctx := errgroup.WithContext(ctx)

	for i, agent := range agents {
		i, agent := i, agent
		if agent.Name == "" {
			agent.Name = fmt.Sprintf("AsyncAgent-%02d", i)
		}

		logger.Debug(fmt.Sprintf("[SERVICE: AsyncAgent][%s] Starting the async agent", agent.Name))

		for i := range agent.Backend {
			agent.Backend[i].Timeout = agent.Consumer.Timeout
		}

		endpoint := &config.EndpointConfig{
			Endpoint:    agent.Name,
			Timeout:     agent.Consumer.Timeout,
			Backend:     agent.Backend,
			ExtraConfig: agent.ExtraConfig,
		}
		p, err := pf.New(endpoint)
		if err != nil {
			logger.Error(fmt.Sprintf("[SERVICE: AsyncAgent][%s] building the proxy pipe:", agent.Name), err)
			continue
		}

		if agent.Connection.MaxRetries <= 0 {
			agent.Connection.MaxRetries = math.MaxInt
		}

		opts := Options{
			Agent:          agent,
			Endpoint:       endpoint,
			Proxy:          p,
			AgentPing:      agentPing,
			G:              g,
			ShouldContinue: func(i int) bool { return i <= agent.Connection.MaxRetries },
			BackoffF:       backoff.GetByName(agent.Connection.BackoffStrategy),
			Logger:         logger,
		}

		for _, f := range a {
			if f(ctx, opts) {
				break
			}
		}

	}

	return g.Wait
}

var ErrNoAgents = errors.New("no agent factories defined")


================================================
FILE: async/asyncagent_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package async

import (
	"context"
	"testing"

	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
	"github.com/luraproject/lura/v2/proxy"
)

func TestAgentStarter_Start_last(t *testing.T) {
	var firstAgentCalled, secondAgentCalled bool
	firstAgent := func(_ context.Context, opts Options) bool {
		// TODO: check opts
		firstAgentCalled = true
		return false
	}
	secondAgent := func(_ context.Context, opts Options) bool {
		// TODO: check opts
		secondAgentCalled = true
		return true
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ch := make(chan string)
	as := AgentStarter([]Factory{firstAgent, secondAgent})
	agents := []*config.AsyncAgent{
		{},
	}
	wait := as.Start(ctx, agents, logging.NoOp, (chan<- string)(ch), noopProxyFactory)

	if err := wait(); err != nil {
		t.Error(err)
	}

	if !firstAgentCalled {
		t.Error("first agent not called")
	}

	if !secondAgentCalled {
		t.Error("second agent not called")
	}
}

func TestAgentStarter_Start_first(t *testing.T) {
	var firstAgentCalled, secondAgentCalled bool
	firstAgent := func(_ context.Context, opts Options) bool {
		firstAgentCalled = true
		return true
	}
	secondAgent := func(_ context.Context, opts Options) bool {
		secondAgentCalled = true
		return false
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ch := make(chan string)
	as := AgentStarter([]Factory{firstAgent, secondAgent})
	agents := []*config.AsyncAgent{
		{},
	}
	wait := as.Start(ctx, agents, logging.NoOp, (chan<- string)(ch), noopProxyFactory)

	if err := wait(); err != nil {
		t.Error(err)
	}

	if !firstAgentCalled {
		t.Error("first agent not called")
	}

	if secondAgentCalled {
		t.Error("second agent called")
	}
}

var noopProxyFactory = proxy.FactoryFunc(func(*config.EndpointConfig) (proxy.Proxy, error) {
	return proxy.NoopProxy, nil
})


================================================
FILE: backoff/backoff.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
Package backoff contains some basic implementations and a selector by strategy name
*/
package backoff

import (
	"math/rand"
	"strings"
	"time"
)

// GetByName returns the WaitBeforeRetry function implementing the strategy
func GetByName(strategy string) TimeToWaitBeforeRetry {
	switch strings.ToLower(strategy) {
	case "linear":
		return LinearBackoff
	case "linear-jitter":
		return LinearJitterBackoff
	case "exponential":
		return ExponentialBackoff
	case "exponential-jitter":
		return ExponentialJitterBackoff
	}
	return DefaultBackoff
}

// TimeToWaitBeforeRetry returns the duration to wait before retrying for the
// given time
type TimeToWaitBeforeRetry func(int) time.Duration

// DefaultBackoffDuration is the duration returned by the DefaultBackoff
var DefaultBackoffDuration = time.Second

// DefaultBackoff always returns DefaultBackoffDuration
func DefaultBackoff(_ int) time.Duration {
	return DefaultBackoffDuration
}

// ExponentialBackoff returns ever increasing backoffs by a power of 2
func ExponentialBackoff(i int) time.Duration {
	return time.Duration(1<<uint(i)) * time.Second
}

// ExponentialJitterBackoff returns ever increasing backoffs by a power of 2
// with +/- 0-33% to prevent sychronized requests.
func ExponentialJitterBackoff(i int) time.Duration {
	return jitter(int(1 << uint(i)))
}

// LinearBackoff returns increasing durations, each a second longer than the last
func LinearBackoff(i int) time.Duration {
	return time.Duration(i) * time.Second
}

// LinearJitterBackoff returns increasing durations, each a second longer than the last
// with +/- 0-33% to prevent sychronized requests.
func LinearJitterBackoff(i int) time.Duration {
	return jitter(i)
}

var random *rand.Rand

func init() {
	random = rand.New(rand.NewSource(time.Now().UnixNano()))
}

// jitter keeps the +/- 0-33% logic in one place
func jitter(i int) time.Duration {
	ms := i * 1000
	maxJitter := ms/3 + 1
	ms += random.Intn(2*maxJitter) - maxJitter
	if ms <= 0 {
		ms = 1
	}

	return time.Duration(ms) * time.Millisecond
}


================================================
FILE: backoff/backoff_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package backoff

import (
	"testing"
	"time"
)

func TestExponentialBackoff(t *testing.T) {
	next := 1
	for i := 0; i < 10; i++ {
		if v := int(ExponentialBackoff(i) / time.Second); v != next {
			t.Errorf("have: %d, want: %d", v, next)
		}
		next *= 2
	}
}

func TestLinearBackoff(t *testing.T) {
	for i := 0; i < 10; i++ {
		if v := int(LinearBackoff(i) / time.Second); v != i {
			t.Errorf("have: %d, want: %d", v, i)
		}
	}
}

func TestDefaultBackoff(t *testing.T) {
	for i := 0; i < 10; i++ {
		if v := int(DefaultBackoff(i) / time.Second); v != 1 {
			t.Errorf("have: %d, want: %d", v, 1)
		}
	}
}


================================================
FILE: config/config.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
Package config defines the config structs and some config parser interfaces and implementations
*/
package config

import (
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/textproto"
	"regexp"
	"sort"
	"strings"
	"time"

	"github.com/luraproject/lura/v2/encoding"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

const (
	// BracketsRouterPatternBuilder uses brackets as route params delimiter
	BracketsRouterPatternBuilder = iota
	// ColonRouterPatternBuilder use a colon as route param delimiter
	ColonRouterPatternBuilder
	// DefaultMaxIdleConnsPerHost is the default value for the MaxIdleConnsPerHost param
	DefaultMaxIdleConnsPerHost = 250
	// DefaultTimeout is the default value to use for the ServiceConfig.Timeout param
	DefaultTimeout = 2 * time.Second

	// ConfigVersion is the current version of the config struct
	ConfigVersion = 3
)

// RoutingPattern to use during route conversion. By default, use the colon router pattern
var RoutingPattern = ColonRouterPatternBuilder

// ServiceConfig defines the lura service
type ServiceConfig struct {
	// name of the service
	Name string `mapstructure:"name"`
	// set of endpoint definitions
	Endpoints []*EndpointConfig `mapstructure:"endpoints"`
	// set of async agent definitions
	AsyncAgents []*AsyncAgent `mapstructure:"async_agent"`
	// defafult timeout
	Timeout time.Duration `mapstructure:"timeout"`
	// default TTL for GET
	CacheTTL time.Duration `mapstructure:"cache_ttl"`
	// default set of hosts
	Host []string `mapstructure:"host"`
	// port to bind the lura service
	Port int `mapstructure:"port"`
	// address to listen
	Address string `mapstructure:"listen_ip"`
	// version code of the configuration
	Version int `mapstructure:"version"`
	// OutputEncoding defines the default encoding strategy to use for the endpoint responses
	OutputEncoding string `mapstructure:"output_encoding"`
	// Extra configuration for customized behaviour
	ExtraConfig ExtraConfig `mapstructure:"extra_config"`

	// ReadTimeout is the maximum duration for reading the entire
	// request, including the body.
	//
	// Because ReadTimeout does not let Handlers make per-request
	// decisions on each request body's acceptable deadline or
	// upload rate, most users will prefer to use
	// ReadHeaderTimeout. It is valid to use them both.
	ReadTimeout time.Duration `mapstructure:"read_timeout"`
	// WriteTimeout is the maximum duration before timing out
	// writes of the response. It is reset whenever a new
	// request's header is read. Like ReadTimeout, it does not
	// let Handlers make decisions on a per-request basis.
	WriteTimeout time.Duration `mapstructure:"write_timeout"`
	// IdleTimeout is the maximum amount of time to wait for the
	// next request when keep-alives are enabled. If IdleTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, ReadHeaderTimeout is used.
	IdleTimeout time.Duration `mapstructure:"idle_timeout"`
	// ReadHeaderTimeout is the amount of time allowed to read
	// request headers. The connection's read deadline is reset
	// after reading the headers and the Handler can decide what
	// is considered too slow for the body.
	ReadHeaderTimeout time.Duration `mapstructure:"read_header_timeout"`
	// MaxHeaderBytes controls the maximum number of bytes the
	// server will read parsing the request header's keys and
	// values, including the request line. It does not limit the
	// size of the request body.
	// If zero, DefaultMaxHeaderBytes (1MB) is used.
	MaxHeaderBytes int `mapstructure:"max_header_bytes"`

	// DisableKeepAlives, if true, prevents re-use of TCP connections
	// between different HTTP requests.
	DisableKeepAlives bool `mapstructure:"disable_keep_alives"`
	// DisableCompression, if true, prevents the Transport from
	// requesting compression with an "Accept-Encoding: gzip"
	// request header when the Request contains no existing
	// Accept-Encoding value. If the Transport requests gzip on
	// its own and gets a gzipped response, it's transparently
	// decoded in the Response.Body. However, if the user
	// explicitly requested gzip it is not automatically
	// uncompressed.
	DisableCompression bool `mapstructure:"disable_compression"`
	// MaxIdleConns controls the maximum number of idle (keep-alive)
	// connections across all hosts. Zero means no limit.
	MaxIdleConns int `mapstructure:"max_idle_connections"`
	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
	// (keep-alive) connections to keep per-host. If zero,
	// DefaultMaxIdleConnsPerHost is used.
	MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"`
	// IdleConnTimeout is the maximum amount of time an idle
	// (keep-alive) connection will remain idle before closing
	// itself.
	// Zero means no limit.
	IdleConnTimeout time.Duration `mapstructure:"idle_connection_timeout"`
	// ResponseHeaderTimeout, if non-zero, specifies the amount of
	// time to wait for a server's response headers after fully
	// writing the request (including its body, if any). This
	// time does not include the time to read the response body.
	ResponseHeaderTimeout time.Duration `mapstructure:"response_header_timeout"`
	// ExpectContinueTimeout, if non-zero, specifies the amount of
	// time to wait for a server's first response headers after fully
	// writing the request headers if the request has an
	// "Expect: 100-continue" header. Zero means no timeout and
	// causes the body to be sent immediately, without
	// waiting for the server to approve.
	// This time does not include the time to send the request header.
	ExpectContinueTimeout time.Duration `mapstructure:"expect_continue_timeout"`
	// DialerTimeout is the maximum amount of time a dial will wait for
	// a connect to complete. If Deadline is also set, it may fail
	// earlier.
	//
	// The default is no timeout.
	//
	// When using TCP and dialing a host name with multiple IP
	// addresses, the timeout may be divided between them.
	//
	// With or without a timeout, the operating system may impose
	// its own earlier timeout. For instance, TCP timeouts are
	// often around 3 minutes.
	DialerTimeout time.Duration `mapstructure:"dialer_timeout"`
	// DialerFallbackDelay specifies the length of time to wait before
	// spawning a fallback connection, when DualStack is enabled.
	// If zero, a default delay of 300ms is used.
	DialerFallbackDelay time.Duration `mapstructure:"dialer_fallback_delay"`
	// DialerKeepAlive specifies the keep-alive period for an active
	// network connection.
	// If zero, keep-alives are not enabled. Network protocols
	// that do not support keep-alives ignore this field.
	DialerKeepAlive time.Duration `mapstructure:"dialer_keep_alive"`

	// DisableStrictREST flags if the REST enforcement is disabled
	DisableStrictREST bool `mapstructure:"disable_rest"`

	// Plugin defines the configuration for the plugin loader
	Plugin *Plugin `mapstructure:"plugin"`

	// TLS defines the configuration params for enabling TLS (HTTPS & HTTP/2) at
	// the router layer
	TLS *TLS `mapstructure:"tls"`

	// UseH2C enables h2c support.
	UseH2C bool `mapstructure:"use_h2c"`

	// run lura in debug mode
	Debug     bool `mapstructure:"debug_endpoint"`
	Echo      bool `mapstructure:"echo_endpoint"`
	uriParser SafeURIParser

	// SequentialStart flags if the agents should be started sequentially
	// before starting the router
	SequentialStart bool `mapstructure:"sequential_start"`

	// AllowInsecureConnections sets the http client tls configuration to allow
	// insecure connections to the backends for development (enables InsecureSkipVerify)
	AllowInsecureConnections bool `mapstructure:"allow_insecure_connections"`

	// ClientTLS is used to configure the http default transport
	// with TLS parameters
	ClientTLS *ClientTLS `mapstructure:"client_tls"`

	// DNSCacheTTL is the duration of the cached data for the DNS lookups
	DNSCacheTTL time.Duration `mapstructure:"dns_cache_ttl"`

	// MaxShutdownDuration is the maximum duration to wait for the graceful shutdown
	// of the service. If 0, it will wait indefinitely until all the requests are served
	// or the process is killed.
	MaxShutdownDuration time.Duration `mapstructure:"max_shutdown_wait_time"`
}

// AsyncAgent defines the configuration of a single subscriber/consumer to be initialized
// and maintained by the lura service
type AsyncAgent struct {
	Name       string     `mapstructure:"name"`
	Connection Connection `mapstructure:"connection"`
	Consumer   Consumer   `mapstructure:"consumer"`
	// the encoding format
	Encoding string `mapstructure:"encoding"`
	// set of definitions of the backends to be linked to this endpoint
	Backend []*Backend `mapstructure:"backend"`

	// Endpoint Extra configuration for customized behaviour
	ExtraConfig ExtraConfig `mapstructure:"extra_config"`
}

type Consumer struct {
	// timeout of the pipe defined by this subscriber
	Timeout time.Duration `mapstructure:"timeout"`
	Workers int           `mapstructure:"workers"`
	Topic   string        `mapstructure:"topic"`
	MaxRate float64       `mapstructure:"max_rate"`
}

type Connection struct {
	MaxRetries      int           `mapstructure:"max_retries"`
	BackoffStrategy string        `mapstructure:"backoff_strategy"`
	HealthInterval  time.Duration `mapstructure:"health_interval"`
}

// EndpointConfig defines the configuration of a single endpoint to be exposed
// by the lura service
type EndpointConfig struct {
	// url pattern to be registered and exposed to the world
	Endpoint string `mapstructure:"endpoint"`
	// HTTP method of the endpoint (GET, POST, PUT, etc)
	Method string `mapstructure:"method"`
	// set of definitions of the backends to be linked to this endpoint
	Backend []*Backend `mapstructure:"backend"`
	// number of concurrent calls this endpoint must send to the backends
	ConcurrentCalls int `mapstructure:"concurrent_calls"`
	// timeout of this endpoint
	Timeout time.Duration `mapstructure:"timeout"`
	// duration of the cache header
	CacheTTL time.Duration `mapstructure:"cache_ttl"`
	// list of query string params to be extracted from the URI
	QueryString []string `mapstructure:"input_query_strings"`
	// Endpoint Extra configuration for customized behaviour
	ExtraConfig ExtraConfig `mapstructure:"extra_config"`
	// HeadersToPass defines the list of headers to pass to the backends
	HeadersToPass []string `mapstructure:"input_headers"`
	// OutputEncoding defines the encoding strategy to use for the endpoint responses
	OutputEncoding string `mapstructure:"output_encoding"`
}

// Backend defines how lura should connect to the backend service (the API resource to consume)
// and how it should process the received response
type Backend struct {
	// Group defines the name of the property the response should be moved to. If empty, the response is
	// not changed
	Group string `mapstructure:"group"`
	// Method defines the HTTP method of the request to send to the backend
	Method string `mapstructure:"method"`
	// Host is a set of hosts of the API
	Host []string `mapstructure:"host"`
	// HostSanitizationDisabled can be set to false if the hostname should be sanitized
	HostSanitizationDisabled bool `mapstructure:"disable_host_sanitize"`
	// URLPattern is the URL pattern to use to locate the resource to be consumed
	URLPattern string `mapstructure:"url_pattern"`
	// AllowList is a set of response fields to allow. If empty, the filter id not used
	AllowList []string `mapstructure:"allow"`
	// DenyList is a set of response fields to remove. If empty, the filter id not used
	DenyList []string `mapstructure:"deny"`
	// map of response fields to be renamed and their new names
	Mapping map[string]string `mapstructure:"mapping"`
	// the encoding format
	Encoding string `mapstructure:"encoding"`
	// the response to process is a collection
	IsCollection bool `mapstructure:"is_collection"`
	// name of the field to extract to the root. If empty, the formater will do nothing
	Target string `mapstructure:"target"`
	// name of the service discovery driver to use
	SD string `mapstructure:"sd"`
	// scheme to use for servers fetched from
	SDScheme string `mapstructure:"sd_scheme"`

	// list of keys to be replaced in the URLPattern
	URLKeys []string
	// number of concurrent calls this endpoint must send to the API
	ConcurrentCalls int
	// timeout of this backend
	Timeout time.Duration
	// decoder to use in order to parse the received response from the API
	Decoder encoding.Decoder `json:"-"`
	// Backend Extra configuration for customized behaviours
	ExtraConfig ExtraConfig `mapstructure:"extra_config"`
	// HeadersToPass defines the list of headers to pass to this backend
	HeadersToPass []string `mapstructure:"input_headers"`
	// QueryStringsToPass has the list of query string params to be sent to the backend
	QueryStringsToPass []string `mapstructure:"input_query_strings"`

	// ParentEndpoint is to be filled by the parent endpoint with its pattern enpoint
	// so logs and other instrumentation can output better info (thus, it is not loaded
	// with `mapstructure` or `json` tags).
	ParentEndpoint string `json:"-" mapstructure:"-"`
	// ParentEndpointMethod is to be filled by the parent endpoint with its enpoint method
	// so logs and other instrumentation can output better info (thus, it is not loaded
	// with `mapstructure` or `json` tags).
	ParentEndpointMethod string `json:"-" mapstructure:"-"`
}

// Plugin contains the config required by the plugin module
type Plugin struct {
	Folder  string `mapstructure:"folder"`
	Pattern string `mapstructure:"pattern"`
}

// TLSKeyPair contains a pair of public and private keys
type TLSKeyPair struct {
	PublicKey  string `mapstructure:"public_key"`
	PrivateKey string `mapstructure:"private_key"`
}

// TLS defines the configuration params for enabling TLS (HTTPS & HTTP/2) at the router layer
type TLS struct {
	IsDisabled               bool         `mapstructure:"disabled"`
	PublicKey                string       `mapstructure:"public_key"`
	PrivateKey               string       `mapstructure:"private_key"`
	CaCerts                  []string     `mapstructure:"ca_certs"`
	MinVersion               string       `mapstructure:"min_version"`
	MaxVersion               string       `mapstructure:"max_version"`
	CurvePreferences         []uint16     `mapstructure:"curve_preferences"`
	PreferServerCipherSuites bool         `mapstructure:"prefer_server_cipher_suites"`
	CipherSuites             []uint16     `mapstructure:"cipher_suites"`
	EnableMTLS               bool         `mapstructure:"enable_mtls"`
	DisableSystemCaPool      bool         `mapstructure:"disable_system_ca_pool"`
	Keys                     []TLSKeyPair `mapstructure:"keys"`
}

// ClientTLS defines the configuration params for an HTTP Client
type ClientTLS struct {
	AllowInsecureConnections bool            `mapstructure:"allow_insecure_connections"`
	CaCerts                  []string        `mapstructure:"ca_certs"`
	DisableSystemCaPool      bool            `mapstructure:"disable_system_ca_pool"`
	MinVersion               string          `mapstructure:"min_version"`
	MaxVersion               string          `mapstructure:"max_version"`
	CurvePreferences         []uint16        `mapstructure:"curve_preferences"`
	CipherSuites             []uint16        `mapstructure:"cipher_suites"`
	ClientCerts              []ClientTLSCert `mapstructure:"client_certs"`
}

// ClientTLSCert holds a certificate with its private key to be
// used for mTLS against the backend services
type ClientTLSCert struct {
	Certificate string `mapstructure:"certificate"`
	PrivateKey  string `mapstructure:"private_key"`
}

// ExtraConfig is a type to store extra configurations for customized behaviours
type ExtraConfig map[string]interface{}

func (e *ExtraConfig) sanitize() {
	for module, extra := range *e {
		if extra, ok := extra.(map[interface{}]interface{}); ok {
			sanitized := map[string]interface{}{}
			for k, v := range extra {
				sanitized[fmt.Sprintf("%v", k)] = v
			}
			(*e)[module] = sanitized
		}
	}
}

func (e *ExtraConfig) Normalize() {
	for module := range *e {
		if alias, ok := ExtraConfigAlias[module]; ok {
			(*e)[alias] = (*e)[module]
			delete(*e, module)
		}
	}
}

// ExtraConfigAlias is the set of alias to accept as namespace
var ExtraConfigAlias = map[string]string{}

var (
	simpleURLKeysPattern    = regexp.MustCompile(`\{([\w\-\.:/]+)\}`)
	sequentialParamsPattern = regexp.MustCompile(`^(resp[\d]+_.+)?(JWT\.([\w\-\.:/]+))?$`)
	invalidPattern          = `^[^/]|\*.|/__(debug|echo|health)(/.*)?$`
	errInvalidHost          = errors.New("invalid host")
	errInvalidNoOpEncoding  = errors.New("can not use NoOp encoding with more than one backends connected to the same endpoint")
	defaultPort             = 8080
)

// Hash returns the sha 256 hash of the configuration in a standard base64 encoded string. It ignores the
// name in order to reduce the noise.
func (s *ServiceConfig) Hash() (string, error) {
	var name string
	name, s.Name = s.Name, ""
	defer func() { s.Name = name }()

	b, err := json.Marshal(s)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256(b)
	return base64.StdEncoding.EncodeToString(sum[:]), nil
}

// Init initializes the configuration struct and its defined endpoints and backends.
// Init also sanitizes the values, applies the default ones whenever necessary and
// normalizes all the things.
func (s *ServiceConfig) Init() error {
	s.uriParser = NewSafeURIParser()

	if s.Version != ConfigVersion {
		return &UnsupportedVersionError{
			Have: s.Version,
			Want: ConfigVersion,
		}
	}

	if err := s.initGlobalParams(); err != nil {
		return err
	}

	if err := s.initAsyncAgents(); err != nil {
		return err
	}

	return s.initEndpoints()
}

func (s *ServiceConfig) Normalize() {
	s.ExtraConfig.Normalize()
	for _, e := range s.Endpoints {
		e.ExtraConfig.Normalize()
		for _, b := range e.Backend {
			b.ExtraConfig.Normalize()
		}
	}
	for _, a := range s.AsyncAgents {
		a.ExtraConfig.Normalize()
		for _, b := range a.Backend {
			b.ExtraConfig.Normalize()
		}
	}
}

func (s *ServiceConfig) initGlobalParams() error {
	if s.Port == 0 {
		s.Port = defaultPort
	}

	if s.Address != "" {
		if !validateAddress(s.Address) {
			return fmt.Errorf("invalid ip address %s", s.Address)
		}
	}

	if s.MaxIdleConnsPerHost == 0 {
		s.MaxIdleConnsPerHost = DefaultMaxIdleConnsPerHost
	}
	if s.Timeout == 0 {
		s.Timeout = DefaultTimeout
	}

	var err error
	s.Host, err = s.uriParser.SafeCleanHosts(s.Host)
	if err != nil {
		return err
	}
	s.ExtraConfig.sanitize()
	return nil
}

func (s *ServiceConfig) initAsyncAgents() error {
	for i, e := range s.AsyncAgents {
		s.initAsyncAgentDefaults(i)

		e.ExtraConfig.sanitize()

		for _, b := range e.Backend {
			if len(b.Host) == 0 {
				b.Host = s.Host
			} else if !b.HostSanitizationDisabled {
				var err error
				b.Host, err = s.uriParser.SafeCleanHosts(b.Host)
				if err != nil {
					return err
				}
			}
			if b.Method == "" {
				b.Method = http.MethodGet
			}
			b.Timeout = e.Consumer.Timeout
			b.Decoder = encoding.GetRegister().Get(strings.ToLower(b.Encoding))(b.IsCollection)

			b.ExtraConfig.sanitize()
		}
	}
	return nil
}

func (s *ServiceConfig) initEndpoints() error {
	for i, e := range s.Endpoints {
		e.Endpoint = s.uriParser.CleanPath(e.Endpoint)

		if err := e.validate(); err != nil {
			return err
		}

		for i := range e.HeadersToPass {
			e.HeadersToPass[i] = textproto.CanonicalMIMEHeaderKey(e.HeadersToPass[i])
		}

		inputParams := s.extractPlaceHoldersFromURLTemplate(e.Endpoint, s.paramExtractionPattern())
		inputSet := map[string]interface{}{}
		for ip := range inputParams {
			inputSet[inputParams[ip]] = nil
		}

		e.Endpoint = s.uriParser.GetEndpointPath(e.Endpoint, inputParams)

		s.initEndpointDefaults(i)

		if e.OutputEncoding == encoding.NOOP && len(e.Backend) > 1 {
			return errInvalidNoOpEncoding
		}

		e.ExtraConfig.sanitize()

		for j, b := range e.Backend {
			// we "tell" the backend which is his parent endpoint
			b.ParentEndpoint = e.Endpoint
			b.ParentEndpointMethod = e.Method
			if err := s.initBackendDefaults(i, j); err != nil {
				return err
			}

			if err := s.initBackendURLMappings(i, j, inputSet); err != nil {
				return err
			}

			b.ExtraConfig.sanitize()
		}
	}
	return nil
}

func (s *ServiceConfig) paramExtractionPattern() *regexp.Regexp {
	if s.DisableStrictREST {
		return simpleURLKeysPattern
	}
	return endpointURLKeysPattern
}

func (*ServiceConfig) extractPlaceHoldersFromURLTemplate(subject string, pattern *regexp.Regexp) []string {
	matches := pattern.FindAllStringSubmatch(subject, -1)
	keys := make([]string, len(matches))
	for k, v := range matches {
		keys[k] = v[1]
	}
	return keys
}

func (s *ServiceConfig) initEndpointDefaults(e int) {
	endpoint := s.Endpoints[e]
	if endpoint.Method == "" {
		endpoint.Method = "GET"
	}
	if s.CacheTTL != 0 && endpoint.CacheTTL == 0 {
		endpoint.CacheTTL = s.CacheTTL
	}
	if s.Timeout != 0 && endpoint.Timeout == 0 {
		endpoint.Timeout = s.Timeout
	}
	if endpoint.ConcurrentCalls == 0 {
		endpoint.ConcurrentCalls = 1
	}
	if endpoint.OutputEncoding == "" {
		if s.OutputEncoding != "" {
			endpoint.OutputEncoding = s.OutputEncoding
		} else {
			endpoint.OutputEncoding = encoding.JSON
		}
	}
}

func (s *ServiceConfig) initAsyncAgentDefaults(e int) {
	agent := s.AsyncAgents[e]
	if s.Timeout != 0 && agent.Consumer.Timeout == 0 {
		agent.Consumer.Timeout = s.Timeout
	}
	if agent.Consumer.Workers < 1 {
		agent.Consumer.Workers = 1
	}
	if agent.Connection.HealthInterval < time.Second {
		agent.Connection.HealthInterval = time.Second
	}
}

func (s *ServiceConfig) initBackendDefaults(e, b int) error {
	endpoint := s.Endpoints[e]
	backend := endpoint.Backend[b]
	if len(backend.Host) == 0 {
		backend.Host = s.Host
	} else if !backend.HostSanitizationDisabled {
		var err error
		backend.Host, err = s.uriParser.SafeCleanHosts(backend.Host)
		if err != nil {
			return err
		}
	}
	if backend.Method == "" {
		backend.Method = endpoint.Method
	}
	if endpoint.OutputEncoding == encoding.NOOP {
		backend.Encoding = encoding.NOOP
	}
	backend.Timeout = endpoint.Timeout
	backend.ConcurrentCalls = endpoint.ConcurrentCalls
	backend.Decoder = encoding.GetRegister().Get(strings.ToLower(backend.Encoding))(backend.IsCollection)

	for i := range backend.HeadersToPass {
		backend.HeadersToPass[i] = textproto.CanonicalMIMEHeaderKey(backend.HeadersToPass[i])
	}
	if backend.SDScheme == "" {
		backend.SDScheme = "http"
	}
	return nil
}

func (s *ServiceConfig) initBackendURLMappings(e, b int, inputParams map[string]interface{}) error {
	backend := s.Endpoints[e].Backend[b]

	backend.URLPattern = s.uriParser.CleanPath(backend.URLPattern)

	outputParams, outputSetSize := uniqueOutput(s.extractPlaceHoldersFromURLTemplate(backend.URLPattern, simpleURLKeysPattern))

	ip := fromSetToSortedSlice(inputParams)

	if outputSetSize > len(ip) {
		return &WrongNumberOfParamsError{
			Endpoint:     s.Endpoints[e].Endpoint,
			Method:       s.Endpoints[e].Method,
			Backend:      b,
			InputParams:  ip,
			OutputParams: outputParams,
		}
	}

	title := cases.Title(language.Und)
	backend.URLKeys = []string{}
	for _, output := range outputParams {
		if !sequentialParamsPattern.MatchString(output) {
			if _, ok := inputParams[output]; !ok {
				return &UndefinedOutputParamError{
					Param:        output,
					Endpoint:     s.Endpoints[e].Endpoint,
					Method:       s.Endpoints[e].Method,
					Backend:      b,
					InputParams:  ip,
					OutputParams: outputParams,
				}
			}
		}
		key := title.String(output[:1]) + output[1:]
		backend.URLPattern = strings.ReplaceAll(backend.URLPattern, "{"+output+"}", "{{."+key+"}}")
		backend.URLKeys = append(backend.URLKeys, key)
	}
	return nil
}

func fromSetToSortedSlice(set map[string]interface{}) []string {
	res := make([]string, 0, len(set))
	for element := range set {
		res = append(res, element)
	}
	sort.Strings(res)
	return res
}

func uniqueOutput(output []string) ([]string, int) {
	sort.Strings(output)
	j := 0
	outputSetSize := 0
	for i := 1; i < len(output); i++ {
		if output[j] == output[i] {
			continue
		}
		if !sequentialParamsPattern.MatchString(output[j]) {
			outputSetSize++
		}
		j++
		output[j] = output[i]
	}
	if j == len(output) {
		return output, outputSetSize
	}
	return output[:j+1], outputSetSize
}

func (e *EndpointConfig) validate() error {
	matched, err := regexp.MatchString(invalidPattern, e.Endpoint)
	if err != nil {
		return &EndpointMatchError{
			Err:    err,
			Path:   e.Endpoint,
			Method: e.Method,
		}
	}
	if matched {
		return &EndpointPathError{Path: e.Endpoint, Method: e.Method}
	}

	if len(e.Backend) == 0 {
		return &NoBackendsError{Path: e.Endpoint, Method: e.Method}
	}
	return nil
}

// EndpointMatchError is the error returned by the configuration init process when the endpoint pattern
// check fails
type EndpointMatchError struct {
	Path   string
	Method string
	Err    error
}

// Error returns a string representation of the EndpointMatchError
func (e *EndpointMatchError) Error() string {
	return fmt.Sprintf("ignoring the '%s %s' endpoint due to a parsing error: %s", e.Method, e.Path, e.Err.Error())
}

// NoBackendsError is the error returned by the configuration init process when an endpoint
// is connected to 0 backends
type NoBackendsError struct {
	Path   string
	Method string
}

// Error returns a string representation of the NoBackendsError
func (n *NoBackendsError) Error() string {
	return "ignoring the '" + n.Method + " " + n.Path + "' endpoint, since it has 0 backends defined!"
}

// UnsupportedVersionError is the error returned by the configuration init process when the configuration
// version is not supported
type UnsupportedVersionError struct {
	Have int
	Want int
}

// Error returns a string representation of the UnsupportedVersionError
func (u *UnsupportedVersionError) Error() string {
	return fmt.Sprintf("unsupported version: %d (want: %d)", u.Have, u.Want)
}

// EndpointPathError is the error returned by the configuration init process when an endpoint
// is using a forbidden path
type EndpointPathError struct {
	Path   string
	Method string
}

// Error returns a string representation of the EndpointPathError
func (e *EndpointPathError) Error() string {
	return "ignoring the '" + e.Method + " " + e.Path + "' endpoint, since it is invalid!!!"
}

// UndefinedOutputParamError is the error returned by the configuration init process when an output
// param is not present in the input param set
type UndefinedOutputParamError struct {
	Endpoint     string
	Method       string
	Backend      int
	InputParams  []string
	OutputParams []string
	Param        string
}

// Error returns a string representation of the UndefinedOutputParamError
func (u *UndefinedOutputParamError) Error() string {
	return fmt.Sprintf(
		"undefined output param '%s'! endpoint: %s %s, backend: %d. input: %v, output: %v",
		u.Param,
		u.Method,
		u.Endpoint,
		u.Backend,
		u.InputParams,
		u.OutputParams,
	)
}

// WrongNumberOfParamsError is the error returned by the configuration init process when the number of output
// params is greatter than the number of input params
type WrongNumberOfParamsError struct {
	Endpoint     string
	Method       string
	Backend      int
	InputParams  []string
	OutputParams []string
}

// Error returns a string representation of the WrongNumberOfParamsError
func (w *WrongNumberOfParamsError) Error() string {
	return fmt.Sprintf(
		"input and output params do not match. endpoint: %s %s, backend: %d. input: %v, output: %v",
		w.Method,
		w.Endpoint,
		w.Backend,
		w.InputParams,
		w.OutputParams,
	)
}

func SetSequentialParamsPattern(pattern string) error {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return err
	}
	sequentialParamsPattern = re
	return nil
}

// SetInvalidPattern sets the invalidPattern variable to the provided value.
func SetInvalidPattern(pattern string) {
	invalidPattern = pattern
}

func validateAddress(address string) bool {
	ip := net.ParseIP(address)
	return ip != nil
}


================================================
FILE: config/config_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package config

import (
	"errors"
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestConfig_rejectInvalidVersion(t *testing.T) {
	subject := ServiceConfig{}
	err := subject.Init()
	if err == nil || strings.Index(err.Error(), "unsupported version: 0 (want: 3)") != 0 {
		t.Error("Error expected. Got", err.Error())
	}
}

func TestConfig_rejectInvalidEndpoints(t *testing.T) {
	samples := []string{
		"/__debug",
		"/__debug/",
		"/__debug/foo",
		"/__debug/foo/bar",
	}

	for _, e := range samples {
		subject := ServiceConfig{Version: ConfigVersion, Endpoints: []*EndpointConfig{{Endpoint: e, Method: "GET"}}}
		err := subject.Init()
		if err == nil || err.Error() != fmt.Sprintf("ignoring the 'GET %s' endpoint, since it is invalid!!!", e) {
			t.Errorf("Unexpected error processing '%s': %v", e, err)
		}
	}
}

func TestConfig_initBackendURLMappings_ok(t *testing.T) {
	samples := []string{
		"supu/{tupu}",
		"/supu/{tupu1}",
		"/supu.local/",
		"supu/{tupu_56}/{supu-5t6}?a={foo}&b={foo}",
		"supu/{tupu_56}{supu-5t6}?a={foo}&b={foo}",
		"supu/tupu{supu-5t6}?a={foo}&b={foo}",
		"{resp0_x}/{tupu1}/{tupu_56}{supu-5t6}?a={tupu}&b={foo}",
		"{resp0_x}/{tupu1}/{JWT.foo}",
		"{resp0_x}/{tupu1}/{JWT.http://example.com/foo_bar}",
	}

	expected := []string{
		"/supu/{{.Tupu}}",
		"/supu/{{.Tupu1}}",
		"/supu.local/",
		"/supu/{{.Tupu_56}}/{{.Supu-5t6}}?a={{.Foo}}&b={{.Foo}}",
		"/supu/{{.Tupu_56}}{{.Supu-5t6}}?a={{.Foo}}&b={{.Foo}}",
		"/supu/tupu{{.Supu-5t6}}?a={{.Foo}}&b={{.Foo}}",
		"/{{.Resp0_x}}/{{.Tupu1}}/{{.Tupu_56}}{{.Supu-5t6}}?a={{.Tupu}}&b={{.Foo}}",
		"/{{.Resp0_x}}/{{.Tupu1}}/{{.JWT.foo}}",
		"/{{.Resp0_x}}/{{.Tupu1}}/{{.JWT.http://example.com/foo_bar}}",
	}

	backend := Backend{}
	endpoint := EndpointConfig{Backend: []*Backend{&backend}}
	subject := ServiceConfig{Endpoints: []*EndpointConfig{&endpoint}, uriParser: NewSafeURIParser()}

	inputSet := map[string]interface{}{
		"tupu":     nil,
		"tupu1":    nil,
		"tupu_56":  nil,
		"supu-5t6": nil,
		"foo":      nil,
	}

	for i := range samples {
		backend.URLPattern = samples[i]
		if err := subject.initBackendURLMappings(0, 0, inputSet); err != nil {
			t.Error(err)
		}
		if backend.URLPattern != expected[i] {
			t.Errorf("want: %s, have: %s\n", expected[i], backend.URLPattern)
		}
	}
}

func TestConfig_initBackendURLMappings_tooManyOutput(t *testing.T) {
	backend := Backend{URLPattern: "supu/{tupu_56}/{supu-5t6}?a={foo}&b={foo}"}
	endpoint := EndpointConfig{
		Method:   "GET",
		Endpoint: "/some/{tupu}",
		Backend:  []*Backend{&backend},
	}
	subject := ServiceConfig{Endpoints: []*EndpointConfig{&endpoint}, uriParser: NewSafeURIParser()}

	inputSet := map[string]interface{}{
		"tupu": nil,
	}

	expectedErrMsg := "input and output params do not match. endpoint: GET /some/{tupu}, backend: 0. input: [tupu], output: [foo supu-5t6 tupu_56]"

	err := subject.initBackendURLMappings(0, 0, inputSet)
	if err == nil || err.Error() != expectedErrMsg {
		t.Errorf("Unexpected error: %v", err)
	}
}

func TestConfig_initBackendURLMappings_undefinedOutput(t *testing.T) {
	backend := Backend{URLPattern: "supu/{tupu_56}/{supu-5t6}?a={foo}&b={foo}"}
	endpoint := EndpointConfig{Endpoint: "/", Method: "GET", Backend: []*Backend{&backend}}
	subject := ServiceConfig{Endpoints: []*EndpointConfig{&endpoint}, uriParser: NewSafeURIParser()}

	inputSet := map[string]interface{}{
		"tupu": nil,
		"supu": nil,
		"foo":  nil,
	}

	expectedErrMsg := "undefined output param 'supu-5t6'! endpoint: GET /, backend: 0. input: [foo supu tupu], output: [foo supu-5t6 tupu_56]"
	err := subject.initBackendURLMappings(0, 0, inputSet)
	if err == nil || err.Error() != expectedErrMsg {
		t.Errorf("error expected. have: %v", err)
	}
}

func TestConfig_init(t *testing.T) {
	supuBackend := Backend{
		URLPattern: "/__debug/supu",
	}
	supuEndpoint := EndpointConfig{
		Endpoint:       "/supu",
		Method:         "post",
		Timeout:        1500 * time.Millisecond,
		CacheTTL:       6 * time.Hour,
		Backend:        []*Backend{&supuBackend},
		OutputEncoding: "some_render",
	}

	githubBackend := Backend{
		URLPattern: "/",
		Host:       []string{"https://api.github.com"},
		AllowList:  []string{"authorizations_url", "code_search_url"},
	}
	githubEndpoint := EndpointConfig{
		Endpoint: "/github",
		Timeout:  1500 * time.Millisecond,
		CacheTTL: 6 * time.Hour,
		Backend:  []*Backend{&githubBackend},
	}

	userBackend := Backend{
		URLPattern: "/users/{user}",
		Host:       []string{"https://jsonplaceholder.typicode.com"},
		Mapping:    map[string]string{"email": "personal_email"},
	}
	rssBackend := Backend{
		URLPattern: "/users/{user}",
		Host:       []string{"https://jsonplaceholder.typicode.com"},
		Encoding:   "rss",
	}
	postBackend := Backend{
		URLPattern: "/posts/{user}",
		Host:       []string{"https://jsonplaceholder.typicode.com"},
		Group:      "posts",
		Encoding:   "xml",
	}
	userEndpoint := EndpointConfig{
		Endpoint: "/users/{user}",
		Backend:  []*Backend{&userBackend, &rssBackend, &postBackend},
	}

	subject := ServiceConfig{
		Version:   ConfigVersion,
		Timeout:   5 * time.Second,
		CacheTTL:  30 * time.Minute,
		Host:      []string{"http://127.0.0.1:8080"},
		Endpoints: []*EndpointConfig{&supuEndpoint, &githubEndpoint, &userEndpoint},
	}

	if err := subject.Init(); err != nil {
		t.Error("Error at the configuration init:", err.Error())
	}

	if len(supuBackend.Host) != 1 || supuBackend.Host[0] != subject.Host[0] {
		t.Error("Default hosts not applied to the supu backend", supuBackend.Host)
	}

	for level, method := range map[string]string{
		"userBackend":  userBackend.Method,
		"postBackend":  postBackend.Method,
		"userEndpoint": userEndpoint.Method,
	} {
		if method != "GET" {
			t.Errorf("Default method not applied at %s. Get: %s", level, method)
		}
	}

	if supuBackend.Method != "post" {
		t.Error("unexpected supuBackend")
	}

	if userBackend.Timeout != subject.Timeout {
		t.Error("default timeout not applied to the userBackend")
	}

	if userEndpoint.CacheTTL != subject.CacheTTL {
		t.Error("default CacheTTL not applied to the userEndpoint")
	}

	hash, err := subject.Hash()
	if err != nil {
		t.Error(err.Error())
	}

	if hash != "/X+fgDf29kmtPpCUh9DeJBOwewpExy3IGEjeqA9zExA=" {
		t.Errorf("unexpected hash: %s", hash)
	}
}

func TestConfig_initKONoBackends(t *testing.T) {
	subject := ServiceConfig{
		Version: ConfigVersion,
		Host:    []string{"http://127.0.0.1:8080"},
		Endpoints: []*EndpointConfig{
			{
				Endpoint: "/supu",
				Method:   "POST",
				Backend:  []*Backend{},
			},
		},
	}

	if err := subject.Init(); err == nil ||
		err.Error() != "ignoring the 'POST /supu' endpoint, since it has 0 backends defined!" {
		t.Error("Unexpected error at the configuration init!", err)
	}
}

func TestConfig_initKOMultipleBackendsForNoopEncoder(t *testing.T) {
	subject := ServiceConfig{
		Version: ConfigVersion,
		Host:    []string{"http://127.0.0.1:8080"},
		Endpoints: []*EndpointConfig{
			{
				Endpoint:       "/supu",
				Method:         "post",
				OutputEncoding: "no-op",
				Backend: []*Backend{
					{
						Encoding: "no-op",
					},
					{
						Encoding: "no-op",
					},
				},
			},
		},
	}

	if err := subject.Init(); err != errInvalidNoOpEncoding {
		t.Error("Expecting an error at the configuration init!", err)
	}
}

func TestConfig_initKOInvalidHost(t *testing.T) {
	subject := ServiceConfig{
		Version: ConfigVersion,
		Host:    []string{"http://127.0.0.1:8080http://127.0.0.1:8080"},
		Endpoints: []*EndpointConfig{
			{
				Endpoint: "/supu",
				Method:   "post",
				Backend:  []*Backend{},
			},
		},
	}

	err := subject.Init()
	if err == nil {
		t.Errorf("expected to fail with invalid host")
		return
	}

	if !errors.Is(err, errInvalidHost) {
		t.Errorf("expected 'errInvalidHost' got: %s", err.Error())
		return
	}
}

func TestConfig_initKOInvalidDebugPattern(t *testing.T) {
	dp := invalidPattern

	invalidPattern = "a(b"
	subject := ServiceConfig{
		Version: ConfigVersion,
		Host:    []string{"http://127.0.0.1:8080"},
		Endpoints: []*EndpointConfig{
			{
				Endpoint: "/__debug/supu",
				Method:   "GET",
				Backend:  []*Backend{},
			},
		},
	}

	if err := subject.Init(); err == nil ||
		err.Error() != "ignoring the 'GET /__debug/supu' endpoint due to a parsing error: error parsing regexp: missing closing ): `a(b`" {
		t.Error("Expecting an error at the configuration init!", err)
	}

	invalidPattern = dp
}

func TestConfig_initKOValidSetinvalidPattern(t *testing.T) {
	dp := invalidPattern

	invalidPattern = `^[^/]|/__(debug|echo|health)(/.*)?$`
	subject := ServiceConfig{
		Version: ConfigVersion,
		Host:    []string{"http://127.0.0.1:8080"},
		Endpoints: []*EndpointConfig{
			{
				Endpoint: "/*",
				Method:   "GET",
				Backend: []*Backend{
					{
						URLPattern: "/",
						Host:       []string{"https://api.github.com"},
						AllowList:  []string{"authorizations_url", "code_search_url"},
					},
				},
			},
		},
	}

	if err := subject.Init(); err != nil {
		t.Error(err)
	}

	invalidPattern = dp
}


================================================
FILE: config/parser.go
================================================
// SPDX-License-Identifier: Apache-2.0

package config

import (
	"encoding/json"
	"fmt"
	"os"
	"time"
)

// Parser reads a configuration file, parses it and returns the content as an init ServiceConfig struct
type Parser interface {
	Parse(configFile string) (ServiceConfig, error)
}

// ParserFunc type is an adapter to allow the use of ordinary functions as subscribers.
// If f is a function with the appropriate signature, ParserFunc(f) is a Parser that calls f.
type ParserFunc func(string) (ServiceConfig, error)

// Parse implements the Parser interface
func (f ParserFunc) Parse(configFile string) (ServiceConfig, error) { return f(configFile) }

// NewParser creates a new parser using the json library
func NewParser() Parser {
	return NewParserWithFileReader(os.ReadFile)
}

// NewParserWithFileReader returns a Parser with the injected FileReaderFunc function
func NewParserWithFileReader(f FileReaderFunc) Parser {
	return parser{fileReader: f}
}

type parser struct {
	fileReader FileReaderFunc
}

// Parser implements the Parse interface
func (p parser) Parse(configFile string) (ServiceConfig, error) {
	var result ServiceConfig
	var cfg parseableServiceConfig
	data, err := p.fileReader(configFile)
	if err != nil {
		return result, CheckErr(err, configFile)
	}
	if err = json.Unmarshal(data, &cfg); err != nil {
		return result, CheckErr(err, configFile)
	}
	result = cfg.normalize()

	if err = result.Init(); err != nil {
		return result, CheckErr(err, configFile)
	}

	return result, nil
}

// CheckErr returns a proper documented error
func CheckErr(err error, configFile string) error {
	switch e := err.(type) {
	case *json.SyntaxError:
		return NewParseError(err, configFile, int(e.Offset))
	case *json.UnmarshalTypeError:
		return NewParseError(err, configFile, int(e.Offset))
	case *os.PathError:
		return fmt.Errorf(
			"'%s' (%s): %s",
			configFile,
			e.Op,
			e.Err.Error(),
		)
	default:
		return fmt.Errorf("'%s': %v", configFile, err)
	}
}

// NewParseError returns a new ParseError
func NewParseError(err error, configFile string, offset int) *ParseError {
	b, _ := os.ReadFile(configFile)
	row, col := getErrorRowCol(b, offset)
	return &ParseError{
		ConfigFile: configFile,
		Err:        err,
		Offset:     offset,
		Row:        row,
		Col:        col,
	}
}

func getErrorRowCol(source []byte, offset int) (row, col int) {
	if len(source) < offset {
		offset = len(source) - 1
	}
	for i := 0; i < offset; i++ {
		v := source[i]
		if v == '\r' {
			continue
		}
		if v == '\n' {
			col = 0
			row++
			continue
		}
		col++
	}
	return
}

// ParseError is an error containing details regarding the row and column where
// an parse error occurred
type ParseError struct {
	ConfigFile string
	Offset     int
	Row        int
	Col        int
	Err        error
}

// Error returns the error message for the ParseError
func (p *ParseError) Error() string {
	return fmt.Sprintf(
		"'%s': %v, offset: %v, row: %v, col: %v",
		p.ConfigFile,
		p.Err.Error(),
		p.Offset,
		p.Row,
		p.Col,
	)
}

// FileReaderFunc is a function used to read the content of a config file
type FileReaderFunc func(string) ([]byte, error)

type parseableServiceConfig struct {
	Name                  string                     `json:"name"`
	Endpoints             []*parseableEndpointConfig `json:"endpoints"`
	AsyncAgents           []*parseableAsyncAgent     `json:"async_agent"`
	Timeout               string                     `json:"timeout"`
	CacheTTL              string                     `json:"cache_ttl"`
	Host                  []string                   `json:"host"`
	Port                  int                        `json:"port"`
	Address               string                     `json:"listen_ip"`
	Version               int                        `json:"version"`
	ExtraConfig           *ExtraConfig               `json:"extra_config,omitempty"`
	ReadTimeout           string                     `json:"read_timeout"`
	WriteTimeout          string                     `json:"write_timeout"`
	IdleTimeout           string                     `json:"idle_timeout"`
	ReadHeaderTimeout     string                     `json:"read_header_timeout"`
	MaxHeaderBytes        int                        `json:"max_header_bytes"`
	DisableKeepAlives     bool                       `json:"disable_keep_alives"`
	DisableCompression    bool                       `json:"disable_compression"`
	DisableStrictREST     bool                       `json:"disable_rest"`
	MaxIdleConns          int                        `json:"max_idle_connections"`
	MaxIdleConnsPerHost   int                        `json:"max_idle_connections_per_host"`
	IdleConnTimeout       string                     `json:"idle_connection_timeout"`
	ResponseHeaderTimeout string                     `json:"response_header_timeout"`
	ExpectContinueTimeout string                     `json:"expect_continue_timeout"`
	OutputEncoding        string                     `json:"output_encoding"`
	DialerTimeout         string                     `json:"dialer_timeout"`
	DialerFallbackDelay   string                     `json:"dialer_fallback_delay"`
	DialerKeepAlive       string                     `json:"dialer_keep_alive"`
	Debug                 bool                       `json:"debug_endpoint"`
	Echo                  bool                       `json:"echo_endpoint"`
	Plugin                *Plugin                    `json:"plugin,omitempty"`
	TLS                   *parseableTLS              `json:"tls,omitempty"`
	ClientTLS             *parseableClientTLS        `json:"client_tls,omitempty"`
	UseH2C                bool                       `json:"use_h2c,omitempty"`
	DNSCacheTTL           string                     `json:"dns_cache_ttl"`
	MaxShutdownDuration   string                     `json:"max_shutdown_wait_time"`
}

func (p *parseableServiceConfig) normalize() ServiceConfig {
	cfg := ServiceConfig{
		Name:                  p.Name,
		Timeout:               parseDuration(p.Timeout),
		CacheTTL:              parseDuration(p.CacheTTL),
		Host:                  p.Host,
		Port:                  p.Port,
		Address:               p.Address,
		Version:               p.Version,
		Debug:                 p.Debug,
		Echo:                  p.Echo,
		ReadTimeout:           parseDuration(p.ReadTimeout),
		WriteTimeout:          parseDuration(p.WriteTimeout),
		IdleTimeout:           parseDuration(p.IdleTimeout),
		ReadHeaderTimeout:     parseDuration(p.ReadHeaderTimeout),
		MaxHeaderBytes:        p.MaxHeaderBytes,
		DisableKeepAlives:     p.DisableKeepAlives,
		DisableCompression:    p.DisableCompression,
		DisableStrictREST:     p.DisableStrictREST,
		MaxIdleConns:          p.MaxIdleConns,
		MaxIdleConnsPerHost:   p.MaxIdleConnsPerHost,
		IdleConnTimeout:       parseDuration(p.IdleConnTimeout),
		ResponseHeaderTimeout: parseDuration(p.ResponseHeaderTimeout),
		ExpectContinueTimeout: parseDuration(p.ExpectContinueTimeout),
		DialerTimeout:         parseDuration(p.DialerTimeout),
		DialerFallbackDelay:   parseDuration(p.DialerFallbackDelay),
		DialerKeepAlive:       parseDuration(p.DialerKeepAlive),
		OutputEncoding:        p.OutputEncoding,
		Plugin:                p.Plugin,
		UseH2C:                p.UseH2C,
		DNSCacheTTL:           parseDuration(p.DNSCacheTTL),
		MaxShutdownDuration:   parseDuration(p.MaxShutdownDuration),
	}
	if p.TLS != nil {
		cfg.TLS = &TLS{
			IsDisabled:               p.TLS.IsDisabled,
			PublicKey:                p.TLS.PublicKey,
			PrivateKey:               p.TLS.PrivateKey,
			CaCerts:                  p.TLS.CaCerts,
			MinVersion:               p.TLS.MinVersion,
			MaxVersion:               p.TLS.MaxVersion,
			CurvePreferences:         p.TLS.CurvePreferences,
			PreferServerCipherSuites: p.TLS.PreferServerCipherSuites,
			CipherSuites:             p.TLS.CipherSuites,
			EnableMTLS:               p.TLS.EnableMTLS,
			DisableSystemCaPool:      p.TLS.DisableSystemCaPool,
		}
		for _, k := range p.TLS.Keys {
			cfg.TLS.Keys = append(cfg.TLS.Keys, TLSKeyPair(k))
		}
	}
	if p.ClientTLS != nil {
		cfg.ClientTLS = &ClientTLS{
			AllowInsecureConnections: p.ClientTLS.AllowInsecureConnections,
			CaCerts:                  p.ClientTLS.CaCerts,
			DisableSystemCaPool:      p.ClientTLS.DisableSystemCaPool,
			MinVersion:               p.ClientTLS.MinVersion,
			MaxVersion:               p.ClientTLS.MaxVersion,
			CurvePreferences:         p.ClientTLS.CurvePreferences,
			CipherSuites:             p.ClientTLS.CipherSuites,
			ClientCerts:              make([]ClientTLSCert, 0, len(p.ClientTLS.ClientCerts)),
		}
		for _, cc := range p.ClientTLS.ClientCerts {
			cfg.ClientTLS.ClientCerts = append(cfg.ClientTLS.ClientCerts, ClientTLSCert(cc))
		}
	}
	if p.ExtraConfig != nil {
		cfg.ExtraConfig = *p.ExtraConfig
	}
	endpoints := make([]*EndpointConfig, 0, len(p.Endpoints))
	for _, e := range p.Endpoints {
		endpoints = append(endpoints, e.normalize())
	}
	cfg.Endpoints = endpoints
	agents := make([]*AsyncAgent, 0, len(p.AsyncAgents))
	for _, a := range p.AsyncAgents {
		agents = append(agents, a.normalize())
	}
	cfg.AsyncAgents = agents
	return cfg
}

type parseableTLSKeyPair struct {
	PublicKey  string `json:"public_key"`
	PrivateKey string `json:"private_key"`
}

type parseableTLS struct {
	IsDisabled               bool                  `json:"disabled"`
	PublicKey                string                `json:"public_key"`
	PrivateKey               string                `json:"private_key"`
	CaCerts                  []string              `json:"ca_certs"`
	MinVersion               string                `json:"min_version"`
	MaxVersion               string                `json:"max_version"`
	CurvePreferences         []uint16              `json:"curve_preferences"`
	PreferServerCipherSuites bool                  `json:"prefer_server_cipher_suites"`
	CipherSuites             []uint16              `json:"cipher_suites"`
	EnableMTLS               bool                  `json:"enable_mtls"`
	DisableSystemCaPool      bool                  `json:"disable_system_ca_pool"`
	Keys                     []parseableTLSKeyPair `json:"keys"`
}

type parseableClientTLS struct {
	AllowInsecureConnections bool                     `json:"allow_insecure_connections"`
	CaCerts                  []string                 `json:"ca_certs"`
	DisableSystemCaPool      bool                     `json:"disable_system_ca_pool"`
	MinVersion               string                   `json:"min_version"`
	MaxVersion               string                   `json:"max_version"`
	CurvePreferences         []uint16                 `json:"curve_preferences"`
	CipherSuites             []uint16                 `json:"cipher_suites"`
	ClientCerts              []parseableClientTLSCert `json:"client_certs"`
}

type parseableClientTLSCert struct {
	Certificate string `json:"certificate"`
	PrivateKey  string `json:"private_key"`
}

type parseableEndpointConfig struct {
	Endpoint        string              `json:"endpoint"`
	Method          string              `json:"method"`
	Backend         []*parseableBackend `json:"backend"`
	ConcurrentCalls int                 `json:"concurrent_calls"`
	Timeout         string              `json:"timeout"`
	CacheTTL        string              `json:"cache_ttl"`
	QueryString     []string            `json:"input_query_strings"`
	ExtraConfig     *ExtraConfig        `json:"extra_config,omitempty"`
	HeadersToPass   []string            `json:"input_headers"`
	OutputEncoding  string              `json:"output_encoding"`
}

func (p *parseableEndpointConfig) normalize() *EndpointConfig {
	e := EndpointConfig{
		Endpoint:        p.Endpoint,
		Method:          p.Method,
		ConcurrentCalls: p.ConcurrentCalls,
		Timeout:         parseDuration(p.Timeout),
		CacheTTL:        parseDuration(p.CacheTTL),
		QueryString:     p.QueryString,
		HeadersToPass:   p.HeadersToPass,
		OutputEncoding:  p.OutputEncoding,
	}
	if p.ExtraConfig != nil {
		e.ExtraConfig = *p.ExtraConfig
	}
	backends := make([]*Backend, 0, len(p.Backend))
	for _, b := range p.Backend {
		backends = append(backends, b.normalize())
	}
	e.Backend = backends
	return &e
}

type parseableAsyncAgent struct {
	Name       string `json:"name"`
	Connection struct {
		MaxRetries      int    `json:"max_retries"`
		BackoffStrategy string `json:"backoff_strategy"`
		HealthInterval  string `json:"health_interval"`
	} `json:"connection"`
	Consumer struct {
		Timeout string  `json:"timeout"`
		Workers int     `json:"workers"`
		Topic   string  `json:"topic"`
		MaxRate float64 `json:"max_rate"`
	} `json:"consumer"`
	Encoding    string              `json:"encoding"`
	Backend     []*parseableBackend `json:"backend"`
	ExtraConfig ExtraConfig         `json:"extra_config"`
}

func (p *parseableAsyncAgent) normalize() *AsyncAgent {
	e := AsyncAgent{
		Name:     p.Name,
		Encoding: p.Encoding,
		Connection: Connection{
			MaxRetries:      p.Connection.MaxRetries,
			BackoffStrategy: p.Connection.BackoffStrategy,
			HealthInterval:  parseDuration(p.Connection.HealthInterval),
		},
		Consumer: Consumer{
			Timeout: parseDuration(p.Consumer.Timeout),
			Workers: p.Consumer.Workers,
			Topic:   p.Consumer.Topic,
			MaxRate: p.Consumer.MaxRate,
		},
	}
	if p.ExtraConfig != nil {
		e.ExtraConfig = p.ExtraConfig
	}
	backends := make([]*Backend, 0, len(p.Backend))
	for _, b := range p.Backend {
		backends = append(backends, b.normalize())
	}
	e.Backend = backends
	return &e
}

type parseableBackend struct {
	Group                    string            `json:"group"`
	Method                   string            `json:"method"`
	Host                     []string          `json:"host"`
	HostSanitizationDisabled bool              `json:"disable_host_sanitize"`
	URLPattern               string            `json:"url_pattern"`
	AllowList                []string          `json:"allow"`
	DenyList                 []string          `json:"deny"`
	Mapping                  map[string]string `json:"mapping"`
	Encoding                 string            `json:"encoding"`
	IsCollection             bool              `json:"is_collection"`
	Target                   string            `json:"target"`
	ExtraConfig              *ExtraConfig      `json:"extra_config,omitempty"`
	SD                       string            `json:"sd"`
	HeadersToPass            []string          `json:"input_headers"`
	SDScheme                 string            `json:"sd_scheme"`
	QueryStringsToPass       []string          `json:"input_query_strings"`
}

func (p *parseableBackend) normalize() *Backend {
	b := Backend{
		Group:                    p.Group,
		Method:                   p.Method,
		Host:                     p.Host,
		HostSanitizationDisabled: p.HostSanitizationDisabled,
		URLPattern:               p.URLPattern,
		Mapping:                  p.Mapping,
		Encoding:                 p.Encoding,
		IsCollection:             p.IsCollection,
		Target:                   p.Target,
		SD:                       p.SD,
		SDScheme:                 p.SDScheme,
		AllowList:                p.AllowList,
		DenyList:                 p.DenyList,
		HeadersToPass:            p.HeadersToPass,
		QueryStringsToPass:       p.QueryStringsToPass,
	}
	if b.SDScheme == "" {
		b.SDScheme = "http"
	}
	if p.ExtraConfig != nil {
		b.ExtraConfig = *p.ExtraConfig
	}
	return &b
}

func parseDuration(v string) time.Duration {
	d, err := time.ParseDuration(v)
	if err != nil {
		return 0
	}
	return d
}


================================================
FILE: config/parser_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package config

import (
	"os"
	"testing"
)

func TestNewParser_ok(t *testing.T) {
	configPath := "/tmp/ok.json"
	configContent := []byte(`{
    "version": 3,
    "name": "My lovely gateway",
    "port": 8080,
    "cache_ttl": "3600s",
    "timeout": "3s",
    "max_header_bytes": 10000,
    "tls": {
		"public_key":  "cert.pem",
		"private_key": "key.pem"
	},
	"async_agent": [
		{
			"name": "agent",
			"connection": {
				"max_retries": 2
			},
			"consumer": {
				"topic": "foo.*"
			},
            "backend": [
                {
                    "host": [
                        "https://api.github.com"
                    ],
                    "url_pattern": "/",
                    "extra_config" : {"user":"test","hits":6,"parents":["gomez","morticia"]}
                }
            ]
		}
	],
    "endpoints": [
        {
            "endpoint": "/github",
            "method": "GET",
            "extra_config" : {"user":"test","hits":6,"parents":["gomez","morticia"]},
            "backend": [
                {
                    "host": [
                        "https://api.github.com"
                    ],
                    "url_pattern": "/",
                    "allow": [
                        "authorizations_url",
                        "code_search_url"
                    ],
                    "extra_config" : {"user":"test","hits":6,"parents":["gomez","morticia"]}
                }
            ]
        },
        {
            "endpoint": "/supu",
            "method": "GET",
            "concurrent_calls": 3,
            "backend": [
                {
                    "host": [
                        "http://127.0.0.1:8080"
                    ],
                    "url_pattern": "/__debug/supu"
                }
            ]
        },
        {
            "endpoint": "/combination/{id}",
            "method": "GET",
            "backend": [
                {
                    "group": "first_post",
                    "host": [
                        "https://jsonplaceholder.typicode.com"
                    ],
                    "url_pattern": "/posts/{id}",
                    "deny": [
                        "userId"
                    ]
                },
                {
                    "host": [
                        "https://jsonplaceholder.typicode.com"
                    ],
                    "url_pattern": "/users/{id}",
                    "mapping": {
                        "email": "personal_email"
                    }
                }
            ]
        }
    ],
    "extra_config" : {"user":"test","hits":6,"parents":["gomez","morticia"]}
}`)
	if err := os.WriteFile(configPath, configContent, 0644); err != nil {
		t.FailNow()
	}

	serviceConfig, err := NewParser().Parse(configPath)

	if err != nil {
		t.Error("Unexpected error. Got", err.Error())
	}
	if serviceConfig.MaxHeaderBytes != 10000 {
		t.Errorf("unexpected max_header_bytes value. have %d, want 10000", serviceConfig.MaxHeaderBytes)
	}
	testExtraConfig(serviceConfig.ExtraConfig, t)

	if endpoints := len(serviceConfig.Endpoints); endpoints != 3 {
		t.Errorf("Unexpected number of endpoints: %d", endpoints)
		return
	}

	endpoint := serviceConfig.Endpoints[0]
	endpointExtraConfiguration := endpoint.ExtraConfig

	if endpointExtraConfiguration != nil {
		testExtraConfig(endpointExtraConfiguration, t)
	} else {
		t.Error("Extra config is not present in EndpointConfig")
	}

	if serviceConfig.TLS == nil {
		t.Error("TLS config not present")
	} else {
		if serviceConfig.TLS.PublicKey != "cert.pem" {
			t.Error("Unexpected TLS Public key")
		}
		if serviceConfig.TLS.PrivateKey != "key.pem" {
			t.Error("Unexpected TLS Private key")
		}
	}

	backend := endpoint.Backend[0]
	backendExtraConfiguration := backend.ExtraConfig
	if backendExtraConfiguration != nil {
		testExtraConfig(backendExtraConfiguration, t)
	} else {
		t.Error("Extra config is not present in BackendConfig")
	}

	if err := os.Remove(configPath); err != nil {
		t.FailNow()
	}

	if l := len(serviceConfig.AsyncAgents); l != 1 {
		t.Errorf("Unexpected number of agents. Have %d, want 1", l)
	}
}

func TestNewParser_errorMessages(t *testing.T) {
	for _, configContent := range []struct {
		name    string
		path    string
		content []byte
		expErr  string
	}{
		{
			name:    "case0",
			path:    "/tmp/ok.json",
			content: []byte(`{`),
			expErr:  "'/tmp/ok.json': unexpected end of JSON input, offset: 1, row: 0, col: 1",
		},
		{
			name:    "case1",
			path:    "/tmp/ok.json",
			content: []byte(`>`),
			expErr:  "'/tmp/ok.json': invalid character '>' looking for beginning of value, offset: 1, row: 0, col: 1",
		},
		{
			name:    "case2",
			path:    "/tmp/ok.json",
			content: []byte(`"`),
			expErr:  "'/tmp/ok.json': unexpected end of JSON input, offset: 1, row: 0, col: 1",
		},
		{
			name:    "case3",
			path:    "/tmp/ok.json",
			content: []byte(``),
			expErr:  "'/tmp/ok.json': unexpected end of JSON input, offset: 0, row: 0, col: 0",
		},
		{
			name:    "case4",
			path:    "/tmp/ok.json",
			content: []byte(`[{}]`),
			expErr:  "'/tmp/ok.json': json: cannot unmarshal array into Go value of type config.parseableServiceConfig, offset: 1, row: 0, col: 1",
		},
		{
			name:    "case5",
			path:    "/tmp/ok.json",
			content: []byte(`42`),
			expErr:  "'/tmp/ok.json': json: cannot unmarshal number into Go value of type config.parseableServiceConfig, offset: 2, row: 0, col: 2",
		},
		{
			name:    "case6",
			path:    "/tmp/ok.json",
			content: []byte("\r\n42"),
			expErr:  "'/tmp/ok.json': json: cannot unmarshal number into Go value of type config.parseableServiceConfig, offset: 4, row: 1, col: 2",
		},
		{
			name: "case7",
			path: "/tmp/ok.json",
			content: []byte(`{
	"version": 3,
	"name": "My lovely gateway",
	"port": 8080,
	"cache_ttl": 3600
	"timeout": "3s",
	"endpoints": []
}`),
			expErr: "'/tmp/ok.json': invalid character '\"' after object key:value pair, offset: 83, row: 5, col: 2",
		},
	} {
		t.Run(configContent.name, func(t *testing.T) {
			if err := os.WriteFile(configContent.path, configContent.content, 0644); err != nil {
				t.Error(err)
				return
			}

			_, err := NewParser().Parse(configContent.path)
			if err == nil {
				t.Errorf("%s: Expecting error", configContent.name)
				return
			}
			if errMsg := err.Error(); errMsg != configContent.expErr {
				t.Errorf("%s: Unexpected error. Got '%s' want '%s'", configContent.name, errMsg, configContent.expErr)
				return
			}

			if err := os.Remove(configContent.path); err != nil {
				t.Errorf("%s: %s", err.Error(), configContent.name)
				return
			}
		})
	}
}

func testExtraConfig(extraConfig map[string]interface{}, t *testing.T) {
	userVar := extraConfig["user"]
	if userVar != "test" {
		t.Error("User in extra config is not test")
	}
	parents, ok := extraConfig["parents"].([]interface{})
	if !ok || parents[0] != "gomez" {
		t.Error("Parent 0 of user us not gomez")
	}
	if !ok || parents[1] != "morticia" {
		t.Error("Parent 1 of user us not morticia")
	}
}

func TestNewParser_unknownFile(t *testing.T) {
	_, err := NewParser().Parse("/nowhere/in/the/fs.json")
	if err == nil || err.Error() != "'/nowhere/in/the/fs.json' (open): no such file or directory" {
		t.Errorf("error expected. got '%v'", err)
	}
}

func TestNewParser_readingError(t *testing.T) {
	wrongConfigPath := "/tmp/reading.json"
	wrongConfigContent := []byte("{hello\ngo\n")
	if err := os.WriteFile(wrongConfigPath, wrongConfigContent, 0644); err != nil {
		t.FailNow()
	}

	expected := "'/tmp/reading.json': invalid character 'h' looking for beginning of object key string, offset: 2, row: 0, col: 2"
	_, err := NewParser().Parse(wrongConfigPath)
	if err == nil || err.Error() != expected {
		t.Error("Error expected. Got", err)
	}
	if err = os.Remove(wrongConfigPath); err != nil {
		t.FailNow()
	}
}

func TestNewParser_initError(t *testing.T) {
	wrongConfigPath := "/tmp/unmarshall.json"
	wrongConfigContent := []byte("{\"a\":42}")
	if err := os.WriteFile(wrongConfigPath, wrongConfigContent, 0644); err != nil {
		t.FailNow()
	}

	_, err := NewParser().Parse(wrongConfigPath)
	if err == nil || err.Error() != "'/tmp/unmarshall.json': unsupported version: 0 (want: 3)" {
		t.Error("Error expected. Got", err)
	}
	if err = os.Remove(wrongConfigPath); err != nil {
		t.FailNow()
	}
}

func TestParserFunc(t *testing.T) {
	expected := ServiceConfig{Version: 42}
	result, err := ParserFunc(func(_ string) (ServiceConfig, error) { return expected, nil })("path/to/the/config/file")
	if err != nil {
		t.Error(err.Error())
	}
	if result.Version != expected.Version {
		t.Error("unexpected parsed config:", result)
	}
}


================================================
FILE: config/uri.go
================================================
// SPDX-License-Identifier: Apache-2.0

package config

import (
	"fmt"
	"regexp"
	"strings"
)

var (
	endpointURLKeysPattern = regexp.MustCompile(`/\{([a-zA-Z\-_0-9]+)\}`)
	hostPattern            = regexp.MustCompile(`(https?://)?([a-zA-Z0-9\._\-]+)(:[0-9]{2,6})?/?`)
)

// URIParser defines the interface for all the URI manipulation required by KrakenD
type URIParser interface {
	CleanHosts([]string) []string
	CleanHost(string) string
	CleanPath(string) string
	GetEndpointPath(string, []string) string
}

// Like URIParser but with safe versions of the clean host functionality that
// does not panic but returns an error.
type SafeURIParser interface {
	SafeCleanHosts([]string) ([]string, error)
	SafeCleanHost(string) (string, error)
	CleanPath(string) string
	GetEndpointPath(string, []string) string
}

// NewURIParser creates a new URIParser using the package variable RoutingPattern
func NewURIParser() URIParser {
	return URI(RoutingPattern)
}

// NewSafeURIParser creates a safe URI parser that does not panic when cleaning hosts
func NewSafeURIParser() URI {
	return URI(RoutingPattern)
}

// URI implements the URIParser interface
type URI int

// SafeCleanHosts applies the SafeCleanHost method to every member of the received array of hosts
func (u URI) SafeCleanHosts(hosts []string) ([]string, error) {
	cleaned := make([]string, 0, len(hosts))
	for i := range hosts {
		h, err := u.SafeCleanHost(hosts[i])
		if err != nil {
			return nil, fmt.Errorf("host %s not valid: %w", hosts[i], errInvalidHost)
		}
		cleaned = append(cleaned, h)
	}
	return cleaned, nil
}

// CleanHosts applies the CleanHost method to every member of the received array of hosts
// Panics in case of error.
func (u URI) CleanHosts(hosts []string) []string {
	ss, e := u.SafeCleanHosts(hosts)
	if e != nil {
		panic(e)
	}
	return ss
}

// SafeCleanHost sanitizes the received host
func (URI) SafeCleanHost(host string) (string, error) {
	matches := hostPattern.FindAllStringSubmatch(host, -1)
	if len(matches) != 1 {
		return "", errInvalidHost
	}
	keys := matches[0][1:]
	if keys[0] == "" {
		keys[0] = "http://"
	}
	return strings.Join(keys, ""), nil
}

// CleanHost sanitizes the received host.
// Panics on error.
func (u URI) CleanHost(host string) string {
	h, err := u.SafeCleanHost(host)
	if err != nil {
		panic(err)
	}
	return h
}

// CleanPath trims all the extra slashes from the received URI path
func (URI) CleanPath(path string) string {
	return "/" + strings.TrimPrefix(path, "/")
}

// GetEndpointPath applies the proper replacement in the received path to generate valid route patterns
func (u URI) GetEndpointPath(path string, params []string) string {
	result := path
	if u == ColonRouterPatternBuilder {
		for p := range params {
			parts := strings.Split(result, "?")
			parts[0] = strings.ReplaceAll(parts[0], "{"+params[p]+"}", ":"+params[p])
			result = strings.Join(parts, "?")
		}
	}
	return result
}


================================================
FILE: config/uri_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package config

import "testing"

func TestURIParser_cleanHosts(t *testing.T) {
	samples := []string{
		"supu",
		"127.0.0.1",
		"https://supu.local/",
		"http://127.0.0.1",
		"supu_42.local:8080/",
		"http://127.0.0.1:8080",
	}

	expected := []string{
		"http://supu",
		"http://127.0.0.1",
		"https://supu.local",
		"http://127.0.0.1",
		"http://supu_42.local:8080",
		"http://127.0.0.1:8080",
	}

	result := NewURIParser().CleanHosts(samples)
	for i := range result {
		if expected[i] != result[i] {
			t.Errorf("want: %s, have: %s\n", expected[i], result[i])
		}
	}
}

func TestURIParser_cleanPath(t *testing.T) {
	samples := []string{
		"supu/{tupu}",
		"supu/{tupu}{supu}",
		"/supu/{tupu}",
		"/supu.local/",
		"supu_supu.txt",
		"supu_42.local?a=8080",
		"supu/supu/supu?a=1&b=2",
		"debug/supu/supu?a=1&b=2",
	}

	expected := []string{
		"/supu/{tupu}",
		"/supu/{tupu}{supu}",
		"/supu/{tupu}",
		"/supu.local/",
		"/supu_supu.txt",
		"/supu_42.local?a=8080",
		"/supu/supu/supu?a=1&b=2",
		"/debug/supu/supu?a=1&b=2",
	}

	subject := URI(BracketsRouterPatternBuilder)

	for i := range samples {
		if have := subject.CleanPath(samples[i]); expected[i] != have {
			t.Errorf("want: %s, have: %s\n", expected[i], have)
		}
	}
}

func TestURIParser_getEndpointPath(t *testing.T) {
	samples := []string{
		"supu/{tupu}",
		"/supu/{tupu}{supu}",
		"/supu/{tupu}",
		"/supu.local/",
		"supu/{tupu}/{supu}?a={s}&b=2",
	}

	expected := []string{
		"supu/:tupu",
		"/supu/:tupu{supu}",
		"/supu/:tupu",
		"/supu.local/",
		"supu/:tupu/:supu?a={s}&b=2",
	}

	sc := ServiceConfig{}
	subject := NewURIParser()

	for i := range samples {
		params := sc.extractPlaceHoldersFromURLTemplate(samples[i], sc.paramExtractionPattern())
		if have := subject.GetEndpointPath(samples[i], params); expected[i] != have {
			t.Errorf("want: %s, have: %s\n", expected[i], have)
		}
	}
}
func TestURIParser_getEndpointPath_notStrictREST(t *testing.T) {
	samples := []string{
		"supu/{tupu}",
		"/supu/{tupu}{supu}",
		"/supu/{tupu}",
		"/supu.local/",
		"supu/{tupu}/{supu}?a={s}&b=2",
	}

	expected := []string{
		"supu/:tupu",
		"/supu/:tupu:supu",
		"/supu/:tupu",
		"/supu.local/",
		"supu/:tupu/:supu?a={s}&b=2",
	}

	sc := ServiceConfig{DisableStrictREST: true}
	subject := NewURIParser()

	for i := range samples {
		params := sc.extractPlaceHoldersFromURLTemplate(samples[i], sc.paramExtractionPattern())
		if have := subject.GetEndpointPath(samples[i], params); expected[i] != have {
			t.Errorf("want: %s, have: %s\n", expected[i], have)
		}
	}
}


================================================
FILE: core/version.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
Package core contains some basic constants and variables
*/
package core

import (
	"fmt"
	"runtime"
	"strings"
)

// KrakendHeaderName is the name of the custom KrakenD header
const KrakendHeaderName = "X-KRAKEND"

// KrakendVersion is the version of the build
var KrakendVersion = "undefined"

// GoVersion is the version of the go compiler used at build time
var GoVersion = strings.TrimPrefix(runtime.Version(), "go")

// GlibcVersion is the version of the glibc used by CGO at build time
var GlibcVersion = "undefined"

// KrakendHeaderValue is the value of the custom KrakenD header
var KrakendHeaderValue = fmt.Sprintf("Version %s", KrakendVersion)

// KrakendUserAgent is the value of the user agent header sent to the backends
var KrakendUserAgent = fmt.Sprintf("KrakenD Version %s", KrakendVersion)


================================================
FILE: docs/BENCHMARKS.md
================================================
Benchmarks
---

Here you'll find some benchmarks of the different components of the Lura framework in several scenarios.

# Proxy components

## Proxy middleware stack

    BenchmarkProxyStack_single-8                   500000          9106 ns/op        1848 B/op         35 allocs/op
    BenchmarkProxyStack_multi/with_1_backends-8    500000          9183 ns/op        1848 B/op         35 allocs/op
    BenchmarkProxyStack_multi/with_2_backends-8    300000         16130 ns/op        3520 B/op         73 allocs/op
    BenchmarkProxyStack_multi/with_3_backends-8    200000         20780 ns/op        5097 B/op        105 allocs/op
    BenchmarkProxyStack_multi/with_4_backends-8    200000         22420 ns/op        6641 B/op        137 allocs/op
    BenchmarkProxyStack_multi/with_5_backends-8    200000         23966 ns/op        8218 B/op        169 allocs/op

## Proxy middlewares

    BenchmarkNewLoadBalancedMiddleware-8                10000000           435 ns/op         328 B/op          6 allocs/op
    BenchmarkNewConcurrentMiddleware_singleNext-8         500000          9351 ns/op        1072 B/op         18 allocs/op
    BenchmarkNewRequestBuilderMiddleware-8              30000000           115 ns/op         160 B/op          2 allocs/op
    BenchmarkNewMergeDataMiddleware/with_2_parts-8       1000000          6746 ns/op        1360 B/op         20 allocs/op
    BenchmarkNewMergeDataMiddleware/with_3_parts-8        500000         10179 ns/op        1488 B/op         22 allocs/op
    BenchmarkNewMergeDataMiddleware/with_4_parts-8        500000         10299 ns/op        1584 B/op         24 allocs/op

# Response manipulation

## Response property whitelisting

    BenchmarkEntityFormatter_whitelistingFilter/with_0_elements_with_0_extra_fields-8           50000000            80.6 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_1_elements_with_0_extra_fields-8           10000000           441 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_2_elements_with_0_extra_fields-8           10000000           474 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_3_elements_with_0_extra_fields-8           10000000           516 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_4_elements_with_0_extra_fields-8           10000000           519 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_0_elements_with_5_extra_fields-8           50000000            84.3 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_1_elements_with_5_extra_fields-8           10000000           565 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_2_elements_with_5_extra_fields-8           10000000           601 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_3_elements_with_5_extra_fields-8           10000000           638 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_4_elements_with_5_extra_fields-8           10000000           627 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_0_elements_with_10_extra_fields-8          50000000            80.7 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_1_elements_with_10_extra_fields-8          10000000           703 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_2_elements_with_10_extra_fields-8           5000000           746 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_3_elements_with_10_extra_fields-8           5000000           779 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_4_elements_with_10_extra_fields-8           5000000           785 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_0_elements_with_15_extra_fields-8          50000000            81.4 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_1_elements_with_15_extra_fields-8           5000000           845 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_2_elements_with_15_extra_fields-8           5000000           886 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_3_elements_with_15_extra_fields-8           5000000           919 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_4_elements_with_15_extra_fields-8           5000000           929 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_0_elements_with_20_extra_fields-8          50000000            80.9 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_1_elements_with_20_extra_fields-8           5000000           988 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_2_elements_with_20_extra_fields-8           5000000           984 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_3_elements_with_20_extra_fields-8           5000000           998 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_4_elements_with_20_extra_fields-8           5000000          1014 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_0_elements_with_25_extra_fields-8          50000000            78.1 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_1_elements_with_25_extra_fields-8           5000000          1149 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_2_elements_with_25_extra_fields-8           3000000          1279 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_3_elements_with_25_extra_fields-8           3000000          1348 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_whitelistingFilter/with_4_elements_with_25_extra_fields-8           3000000          1349 ns/op         384 B/op          3 allocs/op

## Response property blacklisting

    BenchmarkEntityFormatter_blacklistingFilter/with_0_elements_with_0_extra_fields-8           50000000            82.4 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_1_elements_with_0_extra_fields-8           30000000           174 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_2_elements_with_0_extra_fields-8           20000000           205 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_3_elements_with_0_extra_fields-8           100000000           63.5 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_4_elements_with_0_extra_fields-8           100000000           62.9 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_0_elements_with_5_extra_fields-8           50000000            80.5 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_1_elements_with_5_extra_fields-8           30000000           175 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_2_elements_with_5_extra_fields-8           20000000           207 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_3_elements_with_5_extra_fields-8           20000000           255 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_4_elements_with_5_extra_fields-8           20000000           299 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_0_elements_with_10_extra_fields-8          50000000            82.9 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_1_elements_with_10_extra_fields-8          30000000           162 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_2_elements_with_10_extra_fields-8          20000000           193 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_3_elements_with_10_extra_fields-8          20000000           229 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_4_elements_with_10_extra_fields-8          20000000           272 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_0_elements_with_15_extra_fields-8          50000000            76.7 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_1_elements_with_15_extra_fields-8          30000000           161 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_2_elements_with_15_extra_fields-8          20000000           195 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_3_elements_with_15_extra_fields-8          20000000           243 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_4_elements_with_15_extra_fields-8          20000000           292 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_0_elements_with_20_extra_fields-8          50000000            81.4 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_1_elements_with_20_extra_fields-8          30000000           161 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_2_elements_with_20_extra_fields-8          20000000           197 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_3_elements_with_20_extra_fields-8          20000000           239 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_4_elements_with_20_extra_fields-8          20000000           289 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_0_elements_with_25_extra_fields-8          50000000            80.9 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_1_elements_with_25_extra_fields-8          30000000           176 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_2_elements_with_25_extra_fields-8          20000000           200 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_3_elements_with_25_extra_fields-8          20000000           250 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_blacklistingFilter/with_4_elements_with_25_extra_fields-8          20000000           312 ns/op          48 B/op          1 allocs/op

## Response property grouping

    BenchmarkEntityFormatter_grouping/with_0_elements-8             20000000           277 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_grouping/with_5_elements-8             20000000           299 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_grouping/with_10_elements-8            20000000           300 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_grouping/with_15_elements-8            20000000           298 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_grouping/with_20_elements-8            20000000           298 ns/op         384 B/op          3 allocs/op
    BenchmarkEntityFormatter_grouping/with_25_elements-8            20000000           298 ns/op         384 B/op          3 allocs/op

## Response property mapping

    BenchmarkEntityFormatter_mapping/with_0_elements_with_0_extra_fields-8          100000000           61.1 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_1_elements_with_0_extra_fields-8          100000000           63.5 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_2_elements_with_0_extra_fields-8          100000000           61.8 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_3_elements_with_0_extra_fields-8          100000000           63.9 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_4_elements_with_0_extra_fields-8          100000000           63.7 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_5_elements_with_0_extra_fields-8          100000000           64.0 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_0_elements_with_5_extra_fields-8          50000000            81.4 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_1_elements_with_5_extra_fields-8          20000000           177 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_2_elements_with_5_extra_fields-8          20000000           204 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_3_elements_with_5_extra_fields-8          20000000           233 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_4_elements_with_5_extra_fields-8          20000000           266 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_5_elements_with_5_extra_fields-8          20000000           295 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_0_elements_with_10_extra_fields-8         50000000            77.4 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_1_elements_with_10_extra_fields-8         30000000           163 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_2_elements_with_10_extra_fields-8         20000000           198 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_3_elements_with_10_extra_fields-8         20000000           237 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_4_elements_with_10_extra_fields-8         20000000           298 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_5_elements_with_10_extra_fields-8         20000000           331 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_0_elements_with_15_extra_fields-8         50000000            79.5 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_1_elements_with_15_extra_fields-8         30000000           171 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_2_elements_with_15_extra_fields-8         20000000           212 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_3_elements_with_15_extra_fields-8         20000000           265 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_4_elements_with_15_extra_fields-8         20000000           295 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_5_elements_with_15_extra_fields-8         20000000           340 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_0_elements_with_20_extra_fields-8         50000000            77.5 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_1_elements_with_20_extra_fields-8         30000000           163 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_2_elements_with_20_extra_fields-8         20000000           199 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_3_elements_with_20_extra_fields-8         20000000           237 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_4_elements_with_20_extra_fields-8         20000000           287 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_5_elements_with_20_extra_fields-8         20000000           320 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_0_elements_with_25_extra_fields-8         50000000            83.2 ns/op        48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_1_elements_with_25_extra_fields-8         30000000           181 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_2_elements_with_25_extra_fields-8         20000000           222 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_3_elements_with_25_extra_fields-8         20000000           275 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_4_elements_with_25_extra_fields-8         20000000           292 ns/op          48 B/op          1 allocs/op
    BenchmarkEntityFormatter_mapping/with_5_elements_with_25_extra_fields-8         20000000           339 ns/op          48 B/op          1 allocs/op

# Request generator

    BenchmarkRequestGeneratePath//a-8                                         10000000           460 ns/op          96 B/op         10 allocs/op
    BenchmarkRequestGeneratePath//a/{{.Supu}}-8                               10000000           522 ns/op         106 B/op         10 allocs/op
    BenchmarkRequestGeneratePath//a?b={{.Tupu}}-8                             10000000           567 ns/op         136 B/op         10 allocs/op
    BenchmarkRequestGeneratePath//a/{{.Supu}}/foo/{{.Foo}}-8                  10000000           615 ns/op         182 B/op         10 allocs/op
    BenchmarkRequestGeneratePath//a/{{.Supu}}/foo/{{.Foo}}/b?c={{.Tupu}}-8    10000000           655 ns/op         236 B/op         10 allocs/op

# Router Handlers

## Gin

    BenchmarkEndpointHandler_ko-8                1000000          5440 ns/op        3026 B/op         31 allocs/op
    BenchmarkEndpointHandler_ok-8                1000000          6456 ns/op        3393 B/op         36 allocs/op
    BenchmarkEndpointHandler_ko_Parallel-8       5000000          1534 ns/op        3028 B/op         31 allocs/op
    BenchmarkEndpointHandler_ok_Parallel-8       5000000          1846 ns/op        3393 B/op         36 allocs/op

## Mux

    BenchmarkEndpointHandler_ko-8                5000000          1815 ns/op        1088 B/op         13 allocs/op
    BenchmarkEndpointHandler_ok-8                5000000          1693 ns/op        1088 B/op         13 allocs/op
    BenchmarkEndpointHandler_ko_Parallel-8      20000000           558 ns/op        1088 B/op         13 allocs/op
    BenchmarkEndpointHandler_ok_Parallel-8      20000000           597 ns/op        1088 B/op         13 allocs/op


================================================
FILE: docs/CONFIG.md
================================================
# Configuration file

The configuration file needs to be a `json` file. The viper parser supports other formats but they haven't been as tested as the recommended one.

## Json example


    {
	"version": 3,
	"name": "My lovely gateway",
	"port": 8080,
	"timeout": "10s",
	"cache_ttl": "3600s",
	"host": [
		"http://127.0.0.1:8080",
		"http://127.0.0.2:8000",
		"http://127.0.0.3:9000",
		"http://127.0.0.4"
	],
	"endpoints": [{
			"endpoint": "/users/{user}",
			"method": "GET",
			"backend": [{
					"host": [
						"http://127.0.0.3:9000",
						"http://127.0.0.4"
					],
					"url_pattern": "/registered/{user}",
					"allow": [
						"some",
						"what"
					],
					"mapping": {
						"email": "personal_email"
					}
				},
				{
					"host": [
						"http://127.0.0.1:8080"
					],
					"url_pattern": "/users/{user}/permissions",
					"deny": [
						"spam2",
						"notwanted2"
					]
				}
			],
			"concurrent_calls": 2,
			"timeout": "1000s",
			"cache_ttl": 3600,
			"input_query_strings": [
				"page",
				"limit"
			]
		},
		{
			"endpoint": "/foo/bar",
			"method": "POST",
			"backend": [{
				"host": [
					"https://127.0.0.1:8081"
				],
				"url_pattern": "/__debug/tupu"
			}],
			"concurrent_calls": 1,
			"timeout": "1000s",
			"cache_ttl": 3600
		},
		{
			"endpoint": "/github",
			"method": "GET",
			"backend": [{
				"host": [
					"https://api.github.com"
				],
				"url_pattern": "/",
				"allow": [
					"authorizations_url",
					"code_search_url"
				]
			}],
			"concurrent_calls": 2,
			"timeout": "1000s",
			"cache_ttl": 3600
		},
		{
			"endpoint": "/combination/{id}/{supu}",
			"method": "GET",
			"backend": [{
					"group": "first_post",
					"host": [
						"https://jsonplaceholder.typicode.com"
					],
					"url_pattern": "/posts/{id}?supu={supu}",
					"deny": [
						"userId"
					]
				},
				{
					"host": [
						"https://jsonplaceholder.typicode.com"
					],
					"url_pattern": "/users/{id}",
					"mapping": {
						"email": "personal_email"
					}
				}
			],
			"concurrent_calls": 3,
			"timeout": "1000s",
			"input_query_strings": [
				"page",
				"limit"
			]
		}
	]}


================================================
FILE: docs/OVERVIEW.md
================================================
# Overview

## The Lura rules

* [Reactive is key](http://www.reactivemanifesto.org/)
* Reactive is key (yes, it is very very important)
* Failing fast is better than succeeding slow (say it one more time!)
* The simpler, the better
* Everything is plugglable
* Each request must be processed in its own request-scoped context

## The big picture

The Lura framework is composed of a set of packages designed as building blocks for creating pipes and processors between an exposed endpoint and one or several API resources served by your backends.

The most important packages are:

1. the `config` package defines the service.
2. the `router` package sets up the endpoints exposed to the clients.
3. the `proxy` package adds the required middlewares and components for further processing of the requests to send and the received responses sent by the backends, and also to manage the connections against those backends. 

The rest of the packages of the framework contain some helpers and adapters for complementary tasks, like encoding, logging or service discovery.

## The `config` package

The `config` package contains the structs required for the service description.

The `ServiceConfig` struct defines the entire service. It should be initialized before using it in order to be sure that all parameters have been normalized and default values have been applied.

The `config` package also defines an interface for a file config parser and a parser based on the [viper](https://github.com/spf13/viper) library.

## The `router` package

The `router` package contains an interface and several implementations for the Lura router layer using the `mux` router from the `net/http` and the `httprouter` wrapped in the `gin` framework.

The router layer is responsible for setting up the HTTP(S) services, binding the endpoints defined at the `ServiceConfig` struct and transforming the http request into proxy requests before delegating the task to the inner layer (proxy). Once the internal proxy layer returns a proxy response, the router layer converts it into a proper HTTP response and sends it to the user.

This layer can be easily extended in order to use any HTTP router, framework or middleware of your choice. Adding transport layer adapters for other protocols (Thrift, gRPC, AMQP, NATS, etc) is in the roadmap. As always, PRs are welcome!

## The `proxy` package

The `proxy` package is where the most part of the Lura components and features are placed. It defines two important interfaces, designed to be stacked:

* *Proxy* is a function that converts a given context and request into a response.
* *Middleware* is a function that accepts one or more proxies and returns a single proxy wrapping them.

This layer transforms the request received from the outter layer (router) into a single or several requests to your backend services, processes the responses and returns a single response.

Middlewares generates custom proxies that are chained depending on the workflow defined in the configuration until each possible branch ends in a transport-related proxy. Every one of these generated proxies is able to transform the input or even clone it several times and pass it or them to the next element in the chain. Finally, they can also modify the received response or responses adding all kinds of features to the generated pipe.

The Lura framework provides a default implementation of the proxy stack factory.

### Middlewares available

* The `balancing` middleware uses some type of strategy for selecting a backend host to query.
* The `concurrent` middleware improves the QoS by sending several concurrent requests to the next step of the chain and returning the first succesful response using a timeout for canceling the generated workload.
* The `logging` middleware logs the received request and response and also the duration of the segment execution.
* The `merging` middleware is a fork-and-join middleware. It is intended to split the process of the request into several concurrent processes, each one against a different backend, and to merge all the received responses from those created pipes into a single one. It applies a timeout, as the `concurrent` one does.
* The `http` middleware completes the received proxy request by replacing the parameters extracted from the user request in the defined `URLPattern`.

### Proxies available

* The `http` proxy translates a proxy request into an HTTP one, sends it to the backend API using a `HTTPClientFactory`, decodes the returned HTTP response with a `Decoder`, manipulates the response data with an `EntityFormatter` and returns it to the caller.

### Other components of the `proxy` package

The `proxy` package also defines the `EntityFormatter`, the block responsible for enabling a powerful and fast response manipulation.


================================================
FILE: docs/README.md
================================================
<img src="https://luraproject.org/images/lura-logo-header.svg" width="300" />

# The Lura Project

## How to use it

Visit the [framework overview](/docs/OVERVIEW.md) for details about the components of the Lura project.

A good example about how to use it can be found in the [KrakenD CE](https://github.com/krakend/krakend-ce)
API Gateway project.

## Configuration file

[Lura config file](/docs/CONFIG.md).
 
## Benchmarks

Check out the [benchmark results](/docs/BENCHMARKS.md) of several Lura components.

## Contributing

Read the guidelines about [contributing](../CONTRIBUTING.md).



================================================
FILE: encoding/encoding.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
Package encoding provides basic decoding implementations.

Decode decodes HTTP responses:

	resp, _ := http.Get("http://api.example.com/")
	...
	var data map[string]interface{}
	err := JSONDecoder(resp.Body, &data)
*/
package encoding

import (
	"encoding/json"
	"io"
)

// Decoder is a function that reads from the reader and decodes it
// into an map of interfaces
type Decoder func(io.Reader, *map[string]interface{}) error

// DecoderFactory is a function that returns CollectionDecoder or an EntityDecoder
type DecoderFactory func(bool) func(io.Reader, *map[string]interface{}) error

// NOOP is the key for the NoOp encoding
const NOOP = "no-op"

// NoOpDecoder is a decoder that does nothing
func NoOpDecoder(_ io.Reader, _ *map[string]interface{}) error { return nil }

func noOpDecoderFactory(_ bool) func(io.Reader, *map[string]interface{}) error { return NoOpDecoder }

// JSON is the key for the json encoding
const JSON = "json"

// NewJSONDecoder returns the right JSON decoder
func NewJSONDecoder(isCollection bool) func(io.Reader, *map[string]interface{}) error {
	if isCollection {
		return JSONCollectionDecoder
	}
	return JSONDecoder
}

// JSONDecoder decodes a json message into a map
func JSONDecoder(r io.Reader, v *map[string]interface{}) error {
	d := json.NewDecoder(r)
	d.UseNumber()
	return d.Decode(v)
}

// JSONCollectionDecoder decodes a json collection and returns a map with the array at the 'collection' key
func JSONCollectionDecoder(r io.Reader, v *map[string]interface{}) error {
	var collection []interface{}
	d := json.NewDecoder(r)
	d.UseNumber()
	if err := d.Decode(&collection); err != nil {
		return err
	}
	*(v) = map[string]interface{}{"collection": collection}
	return nil
}

// SAFE_JSON is the key for the json encoding
const SAFE_JSON = "safejson"

// NewSafeJSONDecoder returns the universal json decoder
func NewSafeJSONDecoder(_ bool) func(io.Reader, *map[string]interface{}) error {
	return SafeJSONDecoder
}

// SafeJSONDecoder decodes both json objects and collections
func SafeJSONDecoder(r io.Reader, v *map[string]interface{}) error {
	d := json.NewDecoder(r)
	d.UseNumber()
	var t interface{}
	if err := d.Decode(&t); err != nil {
		return err
	}
	switch tt := t.(type) {
	case map[string]interface{}:
		*v = tt
	case []interface{}:
		*v = map[string]interface{}{"collection": tt}
	default:
		*v = map[string]interface{}{"content": tt}
	}
	return nil
}

// STRING is the key for the string encoding
const STRING = "string"

// NewStringDecoder returns a String decoder
func NewStringDecoder(_ bool) func(io.Reader, *map[string]interface{}) error {
	return StringDecoder
}

// StringDecoder returns a map with the content of the reader under the key 'content'
func StringDecoder(r io.Reader, v *map[string]interface{}) error {
	data, err := io.ReadAll(r)
	if err != nil {
		return err
	}
	*(v) = map[string]interface{}{"content": string(data)}
	return nil
}


================================================
FILE: encoding/encoding_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package encoding

import (
	"errors"
	"io"
	"strings"
	"testing"

	"github.com/luraproject/lura/v2/register"
)

func TestNoOpDecoder(t *testing.T) {
	decoders = initDecoderRegister()
	defer func() { decoders = initDecoderRegister() }()

	d := decoders.Get(NOOP)(false)

	errorMsg := erroredReader("this error should never been sent")
	var result map[string]interface{}
	if err := d(errorMsg, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if result != nil {
		t.Error("Unexpected value:", result)
	}
}

func TestRegister(t *testing.T) {
	decoders = initDecoderRegister()
	defer func() { decoders = initDecoderRegister() }()

	original := GetRegister()

	if len(original.data.Clone()) != 4 {
		t.Error("Unexpected number of registered factories:", len(original.data.Clone()))
	}

	decoders = &DecoderRegister{data: register.NewUntyped()}
	decoders.Register("some", NewJSONDecoder)

	if len(decoders.data.Clone()) != 1 {
		t.Error("Unexpected number of registered factories:", len(decoders.data.Clone()))
	}
}

func TestGet(t *testing.T) {
	decoders = initDecoderRegister()
	defer func() { decoders = initDecoderRegister() }()

	if len(decoders.data.Clone()) != 4 {
		t.Error("Unexpected number of registered factories:", len(decoders.data.Clone()))
	}

	checkDecoder(t, JSON)
	checkDecoder(t, "some")

	decoders = &DecoderRegister{data: register.NewUntyped()}
	decoders.Register("some", NewJSONDecoder)

	if len(decoders.data.Clone()) != 1 {
		t.Error("Unexpected number of registered factories:", len(decoders.data.Clone()))
	}

	checkDecoder(t, JSON)
	checkDecoder(t, "some")
}

func TestRegister_complete_ok(t *testing.T) {
	decoders = initDecoderRegister()
	defer func() { decoders = initDecoderRegister() }()

	expectedMsg := "a custom message to decode"
	expectedResponse := map[string]interface{}{"a": 42}

	if err := decoders.Register("custom", func(_ bool) func(io.Reader, *map[string]interface{}) error {
		return func(r io.Reader, v *map[string]interface{}) error {
			d, err := io.ReadAll(r)
			if err != nil {
				t.Error(err)
				return err
			}
			if expectedMsg != string(d) {
				t.Errorf("unexpected msg: %s", string(d))
				return errors.New("unexpected msg to decode")
			}
			*v = expectedResponse
			return nil
		}
	}); err != nil {
		t.Error(err)
		return
	}

	decoder := decoders.Get("custom")(false)
	input := strings.NewReader(expectedMsg)
	var result map[string]interface{}
	if err := decoder(input, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if v, ok := result["a"]; !ok || v.(int) != 42 {
		t.Error("Unexpected value:", result)
	}
}

func TestRegister_complete_ko(t *testing.T) {
	decoders = initDecoderRegister()
	defer func() { decoders = initDecoderRegister() }()

	expectedMsg := "a custom message to decode"
	expectedErr := errors.New("expect me")

	if err := decoders.Register("custom", func(_ bool) func(io.Reader, *map[string]interface{}) error {
		return func(r io.Reader, v *map[string]interface{}) error {
			d, err := io.ReadAll(r)
			if err != nil {
				t.Error(err)
				return err
			}
			if expectedMsg != string(d) {
				t.Errorf("unexpected msg: %s", string(d))
				return errors.New("unexpected msg to decode")
			}
			// v = nil
			return expectedErr
		}
	}); err != nil {
		t.Error(err)
		return
	}

	decoder := decoders.Get("custom")(false)
	input := strings.NewReader(expectedMsg)
	var result map[string]interface{}
	if err := decoder(input, &result); err != expectedErr {
		t.Error("Unexpected error:", err)
	}
	if result != nil {
		t.Error("Unexpected value:", result)
	}
}

func checkDecoder(t *testing.T, name string) {
	d := decoders.Get(name)(false)

	input := strings.NewReader(`{"foo": "bar"}`)
	var result map[string]interface{}
	if err := d(input, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if result["foo"] != "bar" {
		t.Error("Unexpected value:", result["foo"])
	}
}

type erroredReader string

func (e erroredReader) Error() string {
	return string(e)
}

func (e erroredReader) Read(_ []byte) (n int, err error) {
	return 0, e
}


================================================
FILE: encoding/json_benchmark_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package encoding

import (
	"io"
	"strings"
	"testing"
)

func BenchmarkDecoder(b *testing.B) {
	for _, dec := range []struct {
		name    string
		decoder func(io.Reader, *map[string]interface{}) error
	}{
		{
			name:    "json-collection",
			decoder: NewJSONDecoder(true),
		},
		{
			name:    "json-map",
			decoder: NewJSONDecoder(false),
		},
		{
			name:    "safe-json-collection",
			decoder: NewSafeJSONDecoder(true),
		},
		{
			name:    "safe-json-map",
			decoder: NewSafeJSONDecoder(true),
		},
	} {
		for _, tc := range []struct {
			name  string
			input string
		}{
			{
				name:  "collection",
				input: `["a","b","c"]`,
			},
			{
				name:  "map",
				input: `{"foo": "bar", "supu": false, "tupu": 4.20}`,
			},
		} {
			b.Run(dec.name+"/"+tc.name, func(b *testing.B) {
				var result map[string]interface{}
				for i := 0; i < b.N; i++ {
					_ = dec.decoder(strings.NewReader(tc.input), &result)
				}
			})
		}
	}
}


================================================
FILE: encoding/json_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package encoding

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"
)

func ExampleNewJSONDecoder_map() {
	decoder := NewJSONDecoder(false)
	original := strings.NewReader(`{"foo": "bar", "supu": false, "tupu": 4.20}`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		fmt.Println("Unexpected error:", err.Error())
	}
	fmt.Printf("%+v\n", result)

	// output:
	// map[foo:bar supu:false tupu:4.20]
}

func ExampleNewJSONDecoder_collection() {
	decoder := NewJSONDecoder(true)
	original := strings.NewReader(`["foo", "bar", "supu"]`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		fmt.Println("Unexpected error:", err.Error())
	}
	fmt.Printf("%+v\n", result)

	// output:
	// map[collection:[foo bar supu]]
}

func TestNewJSONDecoder_map(t *testing.T) {
	decoder := NewJSONDecoder(false)
	original := strings.NewReader(`{"foo": "bar", "supu": false, "tupu": 4.20}`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if len(result) != 3 {
		t.Error("Unexpected result:", result)
	}
	if v, ok := result["foo"]; !ok || v.(string) != "bar" {
		t.Error("wrong result:", result)
	}
	if v, ok := result["supu"]; !ok || v.(bool) {
		t.Error("wrong result:", result)
	}
	if v, ok := result["tupu"]; !ok || v.(json.Number).String() != "4.20" {
		t.Error("wrong result:", result)
	}
}

func TestNewJSONDecoder_collection(t *testing.T) {
	decoder := NewJSONDecoder(true)
	original := strings.NewReader(`["foo", "bar", "supu"]`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if len(result) != 1 {
		t.Error("Unexpected result:", result)
	}
	v, ok := result["collection"]
	if !ok {
		t.Error("wrong result:", result)
	}
	embedded := v.([]interface{})
	if embedded[0].(string) != "foo" {
		t.Error("wrong result:", result)
	}
	if embedded[1].(string) != "bar" {
		t.Error("wrong result:", result)
	}
	if embedded[2].(string) != "supu" {
		t.Error("wrong result:", result)
	}
}

func TestNewJSONDecoder_ko(t *testing.T) {
	decoder := NewJSONDecoder(true)
	original := strings.NewReader(`3`)
	var result map[string]interface{}
	if err := decoder(original, &result); err == nil {
		t.Error("Expecting error!")
	}
}

func ExampleNewSafeJSONDecoder() {
	decoder := NewSafeJSONDecoder(true)
	for _, body := range []string{
		`{"foo": "bar", "supu": false, "tupu": 4.20}`,
		`["foo", "bar", "supu"]`,
	} {
		var result map[string]interface{}
		if err := decoder(strings.NewReader(body), &result); err != nil {
			fmt.Println("Unexpected error:", err.Error())
		}
		fmt.Printf("%+v\n", result)
	}

	// output:
	// map[foo:bar supu:false tupu:4.20]
	// map[collection:[foo bar supu]]
}

func TestNewSafeJSONDecoder_map(t *testing.T) {
	decoder := NewSafeJSONDecoder(false)
	original := strings.NewReader(`{"foo": "bar", "supu": false, "tupu": 4.20}`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if len(result) != 3 {
		t.Error("Unexpected result:", result)
	}
	if v, ok := result["foo"]; !ok || v.(string) != "bar" {
		t.Error("wrong result:", result)
	}
	if v, ok := result["supu"]; !ok || v.(bool) {
		t.Error("wrong result:", result)
	}
	if v, ok := result["tupu"]; !ok || v.(json.Number).String() != "4.20" {
		t.Error("wrong result:", result)
	}
}

func TestNewSafeJSONDecoder_collection(t *testing.T) {
	decoder := NewSafeJSONDecoder(true)
	original := strings.NewReader(`["foo", "bar", "supu"]`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if len(result) != 1 {
		t.Error("Unexpected result:", result)
	}
	v, ok := result["collection"]
	if !ok {
		t.Error("wrong result:", result)
	}
	embedded := v.([]interface{})
	if embedded[0].(string) != "foo" {
		t.Error("wrong result:", result)
	}
	if embedded[1].(string) != "bar" {
		t.Error("wrong result:", result)
	}
	if embedded[2].(string) != "supu" {
		t.Error("wrong result:", result)
	}
}

func TestNewSafeJSONDecoder_other(t *testing.T) {
	decoder := NewSafeJSONDecoder(true)
	original := strings.NewReader(`3`)
	var result map[string]interface{}
	if err := decoder(original, &result); err != nil {
		t.Error("Unexpected error:", err.Error())
	}
	if v, ok := result["content"]; !ok || v.(json.Number).String() != "3" {
		t.Error("wrong result:", result)
	}
}


================================================
FILE: encoding/register.go
================================================
// SPDX-License-Identifier: Apache-2.0

package encoding

import (
	"io"

	"github.com/luraproject/lura/v2/register"
)

// GetRegister returns the package register
func GetRegister() *DecoderRegister {
	return decoders
}

type untypedRegister interface {
	Register(name string, v interface{})
	Get(name string) (interface{}, bool)
	Clone() map[string]interface{}
}

// DecoderRegister is the struct responsible of registering the decoder factories
type DecoderRegister struct {
	data untypedRegister
}

// Register adds a decoder factory to the register
func (r *DecoderRegister) Register(name string, dec func(bool) func(io.Reader, *map[string]interface{}) error) error {
	r.data.Register(name, dec)
	return nil
}

// Get returns a decoder factory from the register by name. If no factory is found, it returns a JSON decoder factory
func (r *DecoderRegister) Get(name string) func(bool) func(io.Reader, *map[string]interface{}) error {
	for _, n := range []string{name, JSON} {
		if v, ok := r.data.Get(n); ok {
			if dec, ok := v.(func(bool) func(io.Reader, *map[string]interface{}) error); ok {
				return dec
			}
		}
	}
	return NewJSONDecoder
}

var (
	decoders        = initDecoderRegister()
	defaultDecoders = map[string]func(bool) func(io.Reader, *map[string]interface{}) error{
		JSON:      NewJSONDecoder,
		SAFE_JSON: NewSafeJSONDecoder,
		STRING:    NewStringDecoder,
		NOOP:      noOpDecoderFactory,
	}
)

func initDecoderRegister() *DecoderRegister {
	r := &DecoderRegister{data: register.NewUntyped()}
	for k, v := range defaultDecoders {
		r.Register(k, v)
	}
	return r
}


================================================
FILE: go.mod
================================================
module github.com/luraproject/lura/v2

go 1.25.0

require (
	github.com/dimfeld/httptreemux/v5 v5.5.0
	github.com/gin-contrib/sse v1.1.0 // indirect
	github.com/gin-gonic/gin v1.12.0
	github.com/go-chi/chi/v5 v5.2.2
	github.com/gorilla/mux v1.8.1
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/urfave/negroni/v2 v2.0.2
	github.com/valyala/fastrand v1.1.0
)

require (
	github.com/krakend/flatmap v1.2.0
	golang.org/x/net v0.51.0
	golang.org/x/sync v0.19.0
	golang.org/x/text v0.34.0
)

require (
	github.com/bytedance/gopkg v0.1.3 // indirect
	github.com/bytedance/sonic v1.15.0 // indirect
	github.com/bytedance/sonic/loader v0.5.0 // indirect
	github.com/cloudwego/base64x v0.1.6 // indirect
	github.com/gabriel-vasile/mimetype v1.4.12 // indirect
	github.com/go-playground/locales v0.14.1 // indirect
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/go-playground/validator/v10 v10.30.1 // indirect
	github.com/goccy/go-json v0.10.5 // indirect
	github.com/goccy/go-yaml v1.19.2 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/leodido/go-urn v1.4.0 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
	github.com/quic-go/qpack v0.6.0 // indirect
	github.com/quic-go/quic-go v0.59.0 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/ugorji/go/codec v1.3.1 // indirect
	go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
	golang.org/x/arch v0.22.0 // indirect
	golang.org/x/crypto v0.48.0 // indirect
	golang.org/x/sys v0.41.0 // indirect
	google.golang.org/protobuf v1.36.10 // indirect
)


================================================
FILE: go.sum
================================================
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/krakend/flatmap v1.2.0 h1:4NPncAKH7Ca/t878kbGlc/LPWLa+m4sgBhs8aT2Q1SY=
github.com/krakend/flatmap v1.2.0/go.mod h1:FyCOoggdVlWr31+aQaOFvBxlMgYfCE5yuwInLbW1/jM=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/urfave/negroni/v2 v2.0.2 h1:27gJcVxYJ2a/ytEoCHoJ7ybvyhymV4cAhGuMxkyCsrU=
github.com/urfave/negroni/v2 v2.0.2/go.mod h1:SjdApKzYrObukpN/NnlejbQiZWIUjfDFzQltScGYigI=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: logging/log.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
	Package logging provides a simple logger interface and implementations
*/
package logging

import (
	"errors"
	"io"
	"log"
	"os"
	"strings"
)

// Logger collects logging information at several levels
type Logger interface {
	Debug(v ...interface{})
	Info(v ...interface{})
	Warning(v ...interface{})
	Error(v ...interface{})
	Critical(v ...interface{})
	Fatal(v ...interface{})
}

const (
	// LEVEL_DEBUG = 0
	LEVEL_DEBUG = iota
	// LEVEL_INFO = 1
	LEVEL_INFO
	// LEVEL_WARNING = 2
	LEVEL_WARNING
	// LEVEL_ERROR = 3
	LEVEL_ERROR
	// LEVEL_CRITICAL = 4
	LEVEL_CRITICAL
)

var (
	// ErrInvalidLogLevel is used when an invalid log level has been used.
	ErrInvalidLogLevel = errors.New("invalid log level")
	defaultLogger      = BasicLogger{Level: LEVEL_CRITICAL, Logger: log.New(os.Stderr, "", log.LstdFlags)}
	logLevels          = map[string]int{
		"DEBUG":    LEVEL_DEBUG,
		"INFO":     LEVEL_INFO,
		"WARNING":  LEVEL_WARNING,
		"ERROR":    LEVEL_ERROR,
		"CRITICAL": LEVEL_CRITICAL,
	}
	// NoOp is the NO-OP logger
	NoOp, _ = NewLogger("CRITICAL", io.Discard, "")
)

// NewLogger creates and returns a Logger object
func NewLogger(level string, out io.Writer, prefix string) (BasicLogger, error) {
	l, ok := logLevels[strings.ToUpper(level)]
	if !ok {
		return defaultLogger, ErrInvalidLogLevel
	}
	return BasicLogger{Level: l, Prefix: prefix, Logger: log.New(out, "", log.LstdFlags)}, nil
}

type BasicLogger struct {
	Level  int
	Prefix string
	Logger *log.Logger
}

// Debug logs a message using DEBUG as log level.
func (l BasicLogger) Debug(v ...interface{}) {
	if l.Level > LEVEL_DEBUG {
		return
	}
	l.prependLog("DEBUG:", v...)
}

// Info logs a message using INFO as log level.
func (l BasicLogger) Info(v ...interface{}) {
	if l.Level > LEVEL_INFO {
		return
	}
	l.prependLog("INFO:", v...)
}

// Warning logs a message using WARNING as log level.
func (l BasicLogger) Warning(v ...interface{}) {
	if l.Level > LEVEL_WARNING {
		return
	}
	l.prependLog("WARNING:", v...)
}

// Error logs a message using ERROR as log level.
func (l BasicLogger) Error(v ...interface{}) {
	if l.Level > LEVEL_ERROR {
		return
	}
	l.prependLog("ERROR:", v...)
}

// Critical logs a message using CRITICAL as log level.
func (l BasicLogger) Critical(v ...interface{}) {
	l.prependLog("CRITICAL:", v...)
}

// Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1).
func (l BasicLogger) Fatal(v ...interface{}) {
	l.prependLog("FATAL:", v...)
	os.Exit(1)
}

func (l BasicLogger) prependLog(level string, v ...interface{}) {
	msg := make([]interface{}, len(v)+2)
	msg[0] = l.Prefix
	msg[1] = level
	copy(msg[2:], v)
	l.Logger.Println(msg...)
}


================================================
FILE: logging/log_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package logging

import (
	"bytes"
	"os"
	"os/exec"
	"regexp"
	"testing"
)

const (
	debugMsg    = "Debug msg"
	infoMsg     = "Info msg"
	warningMsg  = "Warning msg"
	errorMsg    = "Error msg"
	criticalMsg = "Critical msg"
	fatalMsg    = "Fatal msg"
)

func TestNewLogger(t *testing.T) {
	levels := []string{"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
	regexps := []*regexp.Regexp{
		regexp.MustCompile(debugMsg),
		regexp.MustCompile(infoMsg),
		regexp.MustCompile(warningMsg),
		regexp.MustCompile(errorMsg),
		regexp.MustCompile(criticalMsg),
	}

	for i, level := range levels {
		output := logSomeStuff(level)
		for j := i; j < len(regexps); j++ {
			if !regexps[j].MatchString(output) {
				t.Errorf("The output doesn't contain the expected msg for the level: %s. [%s]", level, output)
			}
		}
	}
}

func TestNewLogger_unknownLevel(t *testing.T) {
	_, err := NewLogger("UNKNOWN", bytes.NewBuffer(make([]byte, 1024)), "pref")
	if err == nil {
		t.Error("The factory didn't return the expected error")
		return
	}
	if err != ErrInvalidLogLevel {
		t.Errorf("The factory didn't return the expected error. Got: %s", err.Error())
	}
}

func TestNewLogger_fatal(t *testing.T) {
	if os.Getenv("BE_CRASHER") == "1" {
		l, err := NewLogger("Critical", bytes.NewBuffer(make([]byte, 1024)), "pref")
		if err != nil {
			t.Error("The factory returned an expected error:", err.Error())
			return
		}
		l.Fatal("crash!!!")
		return
	}
	cmd := exec.Command(os.Args[0], "-test.run=TestNewLogger_fatal")
	cmd.Env = append(os.Environ(), "BE_CRASHER=1")
	err := cmd.Run()
	if e, ok := err.(*exec.ExitError); ok && !e.Success() {
		return
	}
	t.Fatalf("process ran with err %v, want exit status 1", err)
}

func logSomeStuff(level string) string {
	buff := bytes.NewBuffer(make([]byte, 1024))
	logger, _ := NewLogger(level, buff, "pref")

	logger.Debug(debugMsg)
	logger.Info(infoMsg)
	logger.Warning(warningMsg)
	logger.Error(errorMsg)
	logger.Critical(criticalMsg)

	return buff.String()
}


================================================
FILE: plugin/plugin.go
================================================
// SPDX-License-Identifier: Apache-2.0

/*
Package plugin provides tools for loading and registering plugins
*/
package plugin

import (
	"os"
	"path/filepath"
	"strings"
)

// Scan returns all the files contained in the received folder with a filename matching the given pattern
func Scan(folder, pattern string) ([]string, error) {
	files, err := os.ReadDir(folder)
	if err != nil {
		return []string{}, err
	}

	var plugins []string

	for _, file := range files {
		if !file.IsDir() && strings.Contains(file.Name(), pattern) {
			plugins = append(plugins, filepath.Join(folder, file.Name()))
		}
	}

	return plugins, nil
}


================================================
FILE: plugin/plugin_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package plugin

import (
	"os"
	"testing"
)

func TestScan_ok(t *testing.T) {
	tmpDir, err := os.MkdirTemp(".", "test")
	if err != nil {
		t.Error("unexpected error:", err.Error())
		return
	}
	defer os.RemoveAll(tmpDir)
	f, err := os.CreateTemp(tmpDir, "test.so")
	if err != nil {
		t.Error("unexpected error:", err.Error())
		return
	}
	f.Close()
	defer os.RemoveAll(tmpDir)

	tot, err := Scan(tmpDir, ".so")
	if len(tot) != 1 {
		t.Error("unexpected number of plugins found:", tot)
	}
	if err != nil {
		t.Error("unexpected error:", err.Error())
	}
}

func TestScan_noFolder(t *testing.T) {
	expectedErr := "open unknown: no such file or directory"
	tot, err := Scan("unknown", "")
	if len(tot) != 0 {
		t.Error("unexpected number of plugins loaded:", tot)
	}
	if err == nil {
		t.Error("expecting error!")
		return
	}
	if err.Error() != expectedErr {
		t.Error("unexpected error:", err.Error())
	}
}

func TestScan_emptyFolder(t *testing.T) {
	name, err := os.MkdirTemp(".", "test")
	if err != nil {
		t.Error("unexpected error:", err.Error())
		return
	}
	tot, err := Scan(name, "")
	if len(tot) != 0 {
		t.Error("unexpected number of plugins loaded:", tot)
	}
	if err != nil {
		t.Error("unexpected error:", err.Error())
	}
	os.RemoveAll(name)
}

func TestScan_noMatches(t *testing.T) {
	tmpDir, err := os.MkdirTemp(".", "test")
	if err != nil {
		t.Error("unexpected error:", err.Error())
		return
	}
	defer os.RemoveAll(tmpDir)
	f, err := os.CreateTemp(tmpDir, "test")
	if err != nil {
		t.Error("unexpected error:", err.Error())
		return
	}
	f.Close()
	defer os.RemoveAll(tmpDir)
	tot, err := Scan(tmpDir, ".so")
	if len(tot) != 0 {
		t.Error("unexpected number of plugins loaded:", tot)
	}
	if err != nil {
		t.Error("unexpected error:", err.Error())
	}
}


================================================
FILE: proxy/balancing.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"net/url"

	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
	"github.com/luraproject/lura/v2/sd"
)

// NewLoadBalancedMiddleware creates proxy middleware adding the most perfomant balancer
// over a default subscriber
func NewLoadBalancedMiddleware(remote *config.Backend) Middleware {
	return NewLoadBalancedMiddlewareWithSubscriber(sd.GetRegister().Get(remote.SD)(remote))
}

// NewLoadBalancedMiddlewareWithSubscriber creates proxy middleware adding the most perfomant balancer
// over the received subscriber
func NewLoadBalancedMiddlewareWithSubscriber(subscriber sd.Subscriber) Middleware {
	return newLoadBalancedMiddleware(logging.NoOp, sd.NewBalancer(subscriber))
}

// NewRoundRobinLoadBalancedMiddleware creates proxy middleware adding a round robin balancer
// over a default subscriber
func NewRoundRobinLoadBalancedMiddleware(remote *config.Backend) Middleware {
	return NewRoundRobinLoadBalancedMiddlewareWithSubscriber(sd.GetRegister().Get(remote.SD)(remote))
}

// NewRandomLoadBalancedMiddleware creates proxy middleware adding a random balancer
// over a default subscriber
func NewRandomLoadBalancedMiddleware(remote *config.Backend) Middleware {
	return NewRandomLoadBalancedMiddlewareWithSubscriber(sd.GetRegister().Get(remote.SD)(remote))
}

// NewRoundRobinLoadBalancedMiddlewareWithSubscriber creates proxy middleware adding a round robin
// balancer over the received subscriber
func NewRoundRobinLoadBalancedMiddlewareWithSubscriber(subscriber sd.Subscriber) Middleware {
	return newLoadBalancedMiddleware(logging.NoOp, sd.NewRoundRobinLB(subscriber))
}

// NewRandomLoadBalancedMiddlewareWithSubscriber creates proxy middleware adding a random
// balancer over the received subscriber
func NewRandomLoadBalancedMiddlewareWithSubscriber(subscriber sd.Subscriber) Middleware {
	return newLoadBalancedMiddleware(logging.NoOp, sd.NewRandomLB(subscriber))
}

// NewLoadBalancedMiddlewareWithLogger creates proxy middleware adding the most perfomant balancer
// over a default subscriber
func NewLoadBalancedMiddlewareWithLogger(l logging.Logger, remote *config.Backend) Middleware {
	return NewLoadBalancedMiddlewareWithSubscriberAndLogger(l, sd.GetRegister().Get(remote.SD)(remote))
}

// NewLoadBalancedMiddlewareWithSubscriberAndLogger creates proxy middleware adding the most perfomant balancer
// over the received subscriber
func NewLoadBalancedMiddlewareWithSubscriberAndLogger(l logging.Logger, subscriber sd.Subscriber) Middleware {
	return newLoadBalancedMiddleware(l, sd.NewBalancer(subscriber))
}

// NewRoundRobinLoadBalancedMiddlewareWithLogger creates proxy middleware adding a round robin balancer
// over a default subscriber
func NewRoundRobinLoadBalancedMiddlewareWithLogger(l logging.Logger, remote *config.Backend) Middleware {
	return NewRoundRobinLoadBalancedMiddlewareWithSubscriberAndLogger(l, sd.GetRegister().Get(remote.SD)(remote))
}

// NewRandomLoadBalancedMiddlewareWithLogger creates proxy middleware adding a random balancer
// over a default subscriber
func NewRandomLoadBalancedMiddlewareWithLogger(l logging.Logger, remote *config.Backend) Middleware {
	return NewRandomLoadBalancedMiddlewareWithSubscriberAndLogger(l, sd.GetRegister().Get(remote.SD)(remote))
}

// NewRoundRobinLoadBalancedMiddlewareWithSubscriberAndLogger creates proxy middleware adding a round robin
// balancer over the received subscriber
func NewRoundRobinLoadBalancedMiddlewareWithSubscriberAndLogger(l logging.Logger, subscriber sd.Subscriber) Middleware {
	return newLoadBalancedMiddleware(l, sd.NewRoundRobinLB(subscriber))
}

// NewRandomLoadBalancedMiddlewareWithSubscriberAndLogger creates proxy middleware adding a random
// balancer over the received subscriber
func NewRandomLoadBalancedMiddlewareWithSubscriberAndLogger(l logging.Logger, subscriber sd.Subscriber) Middleware {
	return newLoadBalancedMiddleware(l, sd.NewRandomLB(subscriber))
}

func newLoadBalancedMiddleware(l logging.Logger, lb sd.Balancer) Middleware {
	return func(next ...Proxy) Proxy {
		if len(next) > 1 {
			l.Fatal("too many proxies for this proxy middleware: newLoadBalancedMiddleware only accepts 1 proxy, got %d", len(next))
			return nil
		}
		return func(ctx context.Context, r *Request) (*Response, error) {
			host, err := lb.Host()
			if err != nil {
				return nil, err
			}

			r.URL, err = url.Parse(host + r.Path)
			if err != nil {
				return nil, err
			}
			if len(r.Query) > 0 {
				if len(r.URL.RawQuery) > 0 {
					r.URL.RawQuery += "&" + r.Query.Encode()
				} else {
					r.URL.RawQuery += r.Query.Encode()
				}
			}

			return next[0](ctx, r)
		}
	}
}


================================================
FILE: proxy/balancing_benchmark_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"strconv"
	"testing"

	"github.com/luraproject/lura/v2/logging"
)

const veryLargeString = "abcdefghijklmopqrstuvwxyzabcdefghijklmopqrstuvwxyzabcdefghijklmopqrstuvwxyzabcdefghijklmopqrstuvwxyz"

func BenchmarkNewLoadBalancedMiddleware(b *testing.B) {
	for _, tc := range []int{3, 5, 9, 13, 17, 21, 25, 50, 100} {
		b.Run(strconv.Itoa(tc), func(b *testing.B) {
			proxy := newLoadBalancedMiddleware(logging.NoOp, dummyBalancer(veryLargeString[:tc]))(dummyProxy(&Response{}))
			b.ResetTimer()
			b.ReportAllocs()
			for i := 0; i < b.N; i++ {
				proxy(context.Background(), &Request{
					Path: veryLargeString[:tc],
				})
			}
		})
	}
}

func BenchmarkNewLoadBalancedMiddleware_parallel3(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:3])
}

func BenchmarkNewLoadBalancedMiddleware_parallel5(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:5])
}

func BenchmarkNewLoadBalancedMiddleware_parallel9(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:9])
}

func BenchmarkNewLoadBalancedMiddleware_parallel13(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:13])
}

func BenchmarkNewLoadBalancedMiddleware_parallel17(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:17])
}

func BenchmarkNewLoadBalancedMiddleware_parallel21(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:21])
}

func BenchmarkNewLoadBalancedMiddleware_parallel25(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:25])
}

func BenchmarkNewLoadBalancedMiddleware_parallel50(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:50])
}

func BenchmarkNewLoadBalancedMiddleware_parallel100(b *testing.B) {
	benchmarkNewLoadBalancedMiddleware_parallel(b, veryLargeString[:100])
}

func benchmarkNewLoadBalancedMiddleware_parallel(b *testing.B, subject string) {
	b.RunParallel(func(pb *testing.PB) {
		proxy := newLoadBalancedMiddleware(logging.NoOp, dummyBalancer(subject))(dummyProxy(&Response{}))
		for pb.Next() {
			proxy(context.Background(), &Request{
				Path: subject,
			})
		}
	})
}


================================================
FILE: proxy/balancing_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"errors"
	"net"
	"net/url"
	"testing"

	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
	"github.com/luraproject/lura/v2/sd/dnssrv"
)

func TestNewLoadBalancedMiddleware_ok(t *testing.T) {
	want := "supu:8080/tupu"
	lb := newLoadBalancedMiddleware(logging.NoOp, dummyBalancer("supu:8080"))
	assertion := func(ctx context.Context, request *Request) (*Response, error) {
		if request.URL.String() != want {
			t.Errorf("The middleware did not update the request URL! want [%s], have [%s]\n", want, request.URL)
		}
		return nil, nil
	}
	if _, err := lb(assertion)(context.Background(), &Request{
		Path: "/tupu",
	}); err != nil {
		t.Errorf("The middleware propagated an unexpected error: %s\n", err.Error())
	}
}

func TestNewLoadBalancedMiddleware_explosiveBalancer(t *testing.T) {
	expected := errors.New("supu")
	lb := newLoadBalancedMiddleware(logging.NoOp, explosiveBalancer{expected})
	if _, err := lb(explosiveProxy(t))(context.Background(), &Request{}); err != expected {
		t.Errorf("The middleware did not propagate the lb error\n")
	}
}

func TestNewRoundRobinLoadBalancedMiddleware(t *testing.T) {
	testLoadBalancedMw(t, NewRoundRobinLoadBalancedMiddleware(&config.Backend{
		Host: []string{"http://127.0.0.1:8080"},
	}))
}

func TestNewRandomLoadBalancedMiddleware(t *testing.T) {
	testLoadBalancedMw(t, NewRandomLoadBalancedMiddleware(&config.Backend{
		Host: []string{"http://127.0.0.1:8080"},
	}))
}

func testLoadBalancedMw(t *testing.T, lb Middleware) {
	for _, tc := range []struct {
		path     string
		query    url.Values
		expected string
	}{
		{
			path:     "/tupu",
			expected: "http://127.0.0.1:8080/tupu",
		},
		{
			path:     "/tupu?extra=true",
			expected: "http://127.0.0.1:8080/tupu?extra=true",
		},
		{
			path:     "/tupu?extra=true",
			query:    url.Values{"some": []string{"none"}},
			expected: "http://127.0.0.1:8080/tupu?extra=true&some=none",
		},
		{
			path:     "/tupu",
			query:    url.Values{"some": []string{"none"}},
			expected: "http://127.0.0.1:8080/tupu?some=none",
		},
	} {
		assertion := func(ctx context.Context, request *Request) (*Response, error) {
			if request.URL.String() != tc.expected {
				t.Errorf("The middleware did not update the request URL! want [%s], have [%s]\n", tc.expected, request.URL)
			}
			return nil, nil
		}
		if _, err := lb(assertion)(context.Background(), &Request{
			Path:  tc.path,
			Query: tc.query,
		}); err != nil {
			t.Errorf("The middleware propagated an unexpected error: %s\n", err.Error())
		}
	}
}

func TestNewLoadBalancedMiddleware_parsingError(t *testing.T) {
	lb := NewRandomLoadBalancedMiddleware(&config.Backend{
		Host: []string{"127.0.0.1:8080"},
	})
	assertion := func(ctx context.Context, request *Request) (*Response, error) {
		t.Error("The middleware didn't block the request!")
		return nil, nil
	}
	if _, err := lb(assertion)(context.Background(), &Request{
		Path: "/tupu",
	}); err == nil {
		t.Error("The middleware didn't propagate the expected error")
	}
}

func TestNewRoundRobinLoadBalancedMiddleware_DNSSRV(t *testing.T) {
	defaultLookup := dnssrv.DefaultLookup

	dnssrv.DefaultLookup = func(service, proto, name string) (cname string, addrs []*net.SRV, err error) {
		return "cname", []*net.SRV{
			{
				Port:   8080,
				Target: "127.0.0.1",
				Weight: 1,
			},
		}, nil
	}
	testLoadBalancedMw(t, NewRoundRobinLoadBalancedMiddlewareWithSubscriber(dnssrv.New("some.service.example.tld")))

	dnssrv.DefaultLookup = defaultLookup
}

type dummyBalancer string

func (d dummyBalancer) Host() (string, error) { return string(d), nil }

type explosiveBalancer struct {
	Error error
}

func (e explosiveBalancer) Host() (string, error) { return "", e.Error }


================================================
FILE: proxy/concurrent.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
)

// NewConcurrentMiddlewareWithLogger creates a proxy middleware that enables sending several requests concurrently
func NewConcurrentMiddlewareWithLogger(logger logging.Logger, remote *config.Backend) Middleware {
	if remote.ConcurrentCalls == 1 {
		logger.Fatal(fmt.Sprintf("too few concurrent calls for %s %s -> %s: NewConcurrentMiddleware expects more than 1 concurrent call, got %d",
			remote.ParentEndpointMethod, remote.ParentEndpoint, remote.URLPattern, remote.ConcurrentCalls))
		return nil
	}
	serviceTimeout := time.Duration(75*remote.Timeout.Nanoseconds()/100) * time.Nanosecond

	return func(next ...Proxy) Proxy {
		if len(next) > 1 {
			logger.Fatal(fmt.Sprintf("too many proxies for this %s %s -> %s proxy middleware: NewConcurrentMiddleware only accepts 1 proxy, got %d",
				remote.ParentEndpointMethod, remote.ParentEndpoint, remote.URLPattern, len(next)))
			return nil
		}

		return func(ctx context.Context, request *Request) (*Response, error) {
			localCtx, cancel := context.WithTimeout(ctx, serviceTimeout)

			results := make(chan *Response, remote.ConcurrentCalls)
			failed := make(chan error, remote.ConcurrentCalls)

			for i := 0; i < remote.ConcurrentCalls; i++ {
				if i < remote.ConcurrentCalls-1 {
					go processConcurrentCall(localCtx, next[0], CloneRequest(request), results, failed)
				} else {
					go processConcurrentCall(localCtx, next[0], request, results, failed)
				}
			}

			var response *Response
			var err error

			for i := 0; i < remote.ConcurrentCalls; i++ {
				select {
				case response = <-results:
					if response != nil && response.IsComplete {
						cancel()
						return response, nil
					}
				case err = <-failed:
				case <-ctx.Done():
				}
			}
			cancel()
			return response, err
		}
	}
}

// NewConcurrentMiddlewareWithLogger creates a proxy middleware that enables sending several requests concurrently.
// Is recommended to use the version with a logger param.
func NewConcurrentMiddleware(remote *config.Backend) Middleware {
	return NewConcurrentMiddlewareWithLogger(logging.NoOp, remote)
}

var errNullResult = errors.New("invalid response")

func processConcurrentCall(ctx context.Context, next Proxy, request *Request, out chan<- *Response, failed chan<- error) {
	localCtx, cancel := context.WithCancel(ctx)

	result, err := next(localCtx, request)
	if err != nil {
		failed <- err
		cancel()
		return
	}
	if result == nil {
		failed <- errNullResult
		cancel()
		return
	}
	select {
	case out <- result:
	case <-ctx.Done():
		failed <- ctx.Err()
	}
	cancel()
}


================================================
FILE: proxy/concurrent_benchmark_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"testing"
	"time"

	"github.com/luraproject/lura/v2/config"
)

func BenchmarkNewConcurrentMiddleware_singleNext(b *testing.B) {
	backend := config.Backend{
		ConcurrentCalls: 3,
		Timeout:         time.Duration(100) * time.Millisecond,
	}
	proxy := NewConcurrentMiddleware(&backend)(dummyProxy(&Response{}))

	b.ResetTimer()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		proxy(context.Background(), &Request{})
	}
}


================================================
FILE: proxy/concurrent_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"sync/atomic"
	"testing"
	"time"

	"github.com/luraproject/lura/v2/config"
)

func TestNewConcurrentMiddleware_ok(t *testing.T) {
	timeout := 700
	totalCalls := 3
	backend := config.Backend{
		ConcurrentCalls: totalCalls,
		Timeout:         time.Duration(timeout) * time.Millisecond,
	}
	expected := Response{
		Data:       map[string]interface{}{"supu": 42, "tupu": true, "foo": "bar"},
		IsComplete: true,
	}
	mw := NewConcurrentMiddleware(&backend)
	mustEnd := time.After(time.Duration(timeout) * time.Millisecond)
	result, err := mw(dummyProxy(&expected))(context.Background(), &Request{})
	if err != nil {
		t.Errorf("The middleware propagated an unexpected error: %s\n", err.Error())
	}
	select {
	case <-mustEnd:
		t.Errorf("We were expecting a response but we got none\n")
	default:
	}
	if result == nil {
		t.Errorf("The proxy returned a null result\n")
		return
	}
	if !result.IsComplete {
		t.Errorf("The proxy returned an incomplete result: %v\n", result)
	}
	if v, ok := result.Data["supu"]; !ok || v.(int) != 42 {
		t.Errorf("The proxy returned an unexpected result: %v\n", result)
	}
	if v, ok := result.Data["tupu"]; !ok || !v.(bool) {
		t.Errorf("The proxy returned an unexpected result: %v\n", result)
	}
	if v, ok := result.Data["foo"]; !ok || v.(string) != "bar" {
		t.Errorf("The proxy returned an unexpected result: %v\n", result)
	}
}

func TestNewConcurrentMiddleware_okAfterKo(t *testing.T) {
	timeout := 700
	totalCalls := 3
	backend := config.Backend{
		ConcurrentCalls: totalCalls,
		Timeout:         time.Duration(timeout) * time.Millisecond,
	}
	expected := Response{
		Data:       map[string]interface{}{"supu": 42, "tupu": true, "foo": "bar"},
		IsComplete: true,
	}
	mw := NewConcurrentMiddleware(&backend)

	calls := uint64(0)
	mock := func(_ context.Context, _ *Request) (*Response, error) {
		total := atomic.AddUint64(&calls, 1)
		if total%2 == 0 {
			return &expected, nil
		}
		return nil, nil
	}
	mustEnd := time.After(time.Duration(timeout) * time.Millisecond)
	result, err := mw(mock)(context.Background(), &Request{})

	if result == nil {
		t.Errorf("The proxy returned a null result\n")
		return
	}
	if err != nil {
		t.Errorf("The middleware propagated an unexpected error: %s\n", err.Error())
	}
	select {
	case <-mustEnd:
		t.Errorf("We were expecting a response but we got none\n")
	default:
	}
	if !result.IsComplete {
		t.Errorf("The proxy returned an incomplete result: %v\n", result)
	}
	if v, ok := result.Data["supu"]; !ok || v.(int) != 42 {
		t.Errorf("The proxy returned an unexpected result: %v\n", result)
	}
	if v, ok := result.Data["tupu"]; !ok || !v.(bool) {
		t.Errorf("The proxy returned an unexpected result: %v\n", result)
	}
	if v, ok := result.Data["foo"]; !ok || v.(string) != "bar" {
		t.Errorf("The proxy returned an unexpected result: %v\n", result)
	}
}

func TestNewConcurrentMiddleware_timeout(t *testing.T) {
	timeout := 100
	totalCalls := 3
	backend := config.Backend{
		ConcurrentCalls: totalCalls,
		Timeout:         time.Duration(timeout) * time.Millisecond,
	}
	mw := NewConcurrentMiddleware(&backend)
	mustEnd := time.After(time.Duration(2*timeout) * time.Millisecond)

	response, err := mw(delayedProxy(t, time.Duration(5*timeout)*time.Millisecond, &Response{}))(context.Background(), &Request{})
	if err == nil || err.Error() != "context deadline exceeded" {
		t.Errorf("The middleware didn't propagate a timeout error: %s\n", err)
	}
	if response != nil {
		t.Errorf("We weren't expecting a response but we got one: %v\n", response)
		return
	}
	select {
	case <-mustEnd:
		t.Errorf("We were expecting a response at this point in time!\n")
		return
	default:
	}
}


================================================
FILE: proxy/factory.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
	"github.com/luraproject/lura/v2/sd"
)

// Factory creates proxies based on the received endpoint configuration.
//
// Both, factories and backend factories, create proxies but factories are designed as a stack makers
// because they are intended to generate the complete proxy stack for a given frontend endpoint
// the app would expose and they could wrap several proxies provided by a backend factory
type Factory interface {
	New(cfg *config.EndpointConfig) (Proxy, error)
}

// FactoryFunc type is an adapter to allow the use of ordinary functions as proxy factories.
// If f is a function with the appropriate signature, FactoryFunc(f) is a Factory that calls f.
type FactoryFunc func(*config.EndpointConfig) (Proxy, error)

// New implements the Factory interface
func (f FactoryFunc) New(cfg *config.EndpointConfig) (Proxy, error) { return f(cfg) }

// DefaultFactory returns a default http proxy factory with the injected logger
func DefaultFactory(logger logging.Logger) Factory {
	return NewDefaultFactory(httpProxy, logger)
}

// DefaultFactoryWithSubscriber returns a default proxy factory with the injected logger and subscriber factory
func DefaultFactoryWithSubscriber(logger logging.Logger, sF sd.SubscriberFactory) Factory {
	return NewDefaultFactoryWithSubscriber(httpProxy, logger, sF)
}

// NewDefaultFactory returns a default proxy factory with the injected proxy builder and logger
func NewDefaultFactory(backendFactory BackendFactory, logger logging.Logger) Factory {
	sf := func(remote *config.Backend) sd.Subscriber {
		return sd.GetRegister().Get(remote.SD)(remote)
	}
	return NewDefaultFactoryWithSubscriber(backendFactory, logger, sf)
}

// NewDefaultFactoryWithSubscriber returns a default proxy factory with the injected proxy builder,
// logger and subscriber factory
func NewDefaultFactoryWithSubscriber(backendFactory BackendFactory, logger logging.Logger, sF sd.SubscriberFactory) Factory {
	return defaultFactory{backendFactory, logger, sF}
}

type defaultFactory struct {
	backendFactory    BackendFactory
	logger            logging.Logger
	subscriberFactory sd.SubscriberFactory
}

// New implements the Factory interface
func (pf defaultFactory) New(cfg *config.EndpointConfig) (p Proxy, err error) {
	switch len(cfg.Backend) {
	case 0:
		err = ErrNoBackends
	case 1:
		p, err = pf.newSingle(cfg)
	default:
		p, err = pf.newMulti(cfg)
	}
	if err != nil {
		return
	}

	p = NewPluginMiddleware(pf.logger, cfg)(p)
	p = NewStaticMiddleware(pf.logger, cfg)(p)
	return
}

func (pf defaultFactory) newMulti(cfg *config.EndpointConfig) (p Proxy, err error) {
	backendProxy := make([]Proxy, len(cfg.Backend))
	for i, backend := range cfg.Backend {
		backendProxy[i] = pf.newStack(backend)
	}
	p = NewMergeDataMiddleware(pf.logger, cfg)(backendProxy...)
	p = NewFlatmapMiddleware(pf.logger, cfg)(p)
	return
}

func (pf defaultFactory) newSingle(cfg *config.EndpointConfig) (Proxy, error) {
	return pf.newStack(cfg.Backend[0]), nil
}

func (pf defaultFactory) newStack(backend *config.Backend) (p Proxy) {
	p = pf.backendFactory(backend)
	p = NewBackendPluginMiddleware(pf.logger, backend)(p)
	p = NewGraphQLMiddleware(pf.logger, backend)(p)
	p = NewFilterHeadersMiddleware(pf.logger, backend)(p)
	p = NewLoadBalancedMiddlewareWithSubscriberAndLogger(pf.logger, pf.subscriberFactory(backend))(p)
	if backend.ConcurrentCalls > 1 {
		p = NewConcurrentMiddlewareWithLogger(pf.logger, backend)(p)
	}
	p = NewRequestBuilderMiddlewareWithLogger(pf.logger, backend)(p)
	// we need to filter the input query strings before the request is constructed
	// so the query strings are properly added to the URL:
	p = NewFilterQueryStringsMiddleware(pf.logger, backend)(p)
	return
}


================================================
FILE: proxy/factory_test.go
================================================
//go:build integration || !race
// +build integration !race

// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"bytes"
	"context"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
	"github.com/luraproject/lura/v2/sd"
)

func TestFactoryFunc(t *testing.T) {
	buff := bytes.NewBuffer(make([]byte, 1024))
	logger, err := logging.NewLogger("ERROR", buff, "pref")
	if err != nil {
		t.Error("building the logger:", err.Error())
		return
	}

	factory := FactoryFunc(func(cfg *config.EndpointConfig) (Proxy, error) { return DefaultFactory(logger).New(cfg) })

	if _, err := factory.New(&config.EndpointConfig{}); err != ErrNoBackends {
		t.Errorf("Expecting ErrNoBackends. Got: %v\n", err)
	}
}

func TestDefaultFactoryWithSubscriber(t *testing.T) {
	buff := bytes.NewBuffer(make([]byte, 1024))
	logger, err := logging.NewLogger("ERROR", buff, "pref")
	if err != nil {
		t.Error("building the logger:", err.Error())
		return
	}

	factory := DefaultFactoryWithSubscriber(logger, sd.FixedSubscriberFactory)

	if _, err := factory.New(&config.EndpointConfig{}); err != ErrNoBackends {
		t.Errorf("Expecting ErrNoBackends. Got: %v\n", err)
	}
}

func TestDefaultFactory_noBackends(t *testing.T) {
	buff := bytes.NewBuffer(make([]byte, 1024))
	logger, err := logging.NewLogger("ERROR", buff, "pref")
	if err != nil {
		t.Error("building the logger:", err.Error())
		return
	}
	factory := DefaultFactory(logger)

	if _, err := factory.New(&config.EndpointConfig{}); err != ErrNoBackends {
		t.Errorf("Expecting ErrNoBackends. Got: %v\n", err)
	}
}

func TestNewDefaultFactory_ok(t *testing.T) {
	buff := bytes.NewBuffer(make([]byte, 1024))
	logger, err := logging.NewLogger("ERROR", buff, "pref")
	if err != nil {
		t.Error("building the logger:", err.Error())
		return
	}
	expectedResponse := Response{
		IsComplete: true,
		Data:       map[string]interface{}{"foo": "bar"},
	}
	expectedMethod := "SOME"
	expectedHost := "http://example.com/"
	expectedPath := "/foo"
	expectedURL := expectedHost + strings.TrimLeft(expectedPath, "/")

	URL, err := url.Parse(expectedHost)
	if err != nil {
		t.Errorf("building the sample url: %s\n", err.Error())
	}

	request := Request{
		Method: expectedMethod,
		Path:   expectedPath,
		URL:    URL,
		Body:   newDummyReadCloser(""),
	}

	assertion := func(ctx context.Context, request *Request) (*Response, error) {
		if request.URL.String() != expectedURL {
			t.Errorf("The middlewares did not update the request URL! want [%s], have [%s]\n", expectedURL, request.URL)
		}
		return &expectedResponse, nil
	}
	factory := NewDefaultFactory(func(_ *config.Backend) Proxy { return assertion }, logger)

	backend := config.Backend{
		URLPattern: expectedPath,
		Method:     expectedMethod,
	}
	endpointSingle := config.EndpointConfig{
		Backend: []*config.Backend{&backend},
	}
	endpointMulti := config.EndpointConfig{
		Backend:         []*config.Backend{&backend, &backend},
		ConcurrentCalls: 3,
	}
	serviceConfig := config.ServiceConfig{
		Version:   config.ConfigVersion,
		Endpoints: []*config.EndpointConfig{&endpointSingle, &endpointMulti},
		Timeout:   100 * time.Millisecond,
		Host:      []string{expectedHost},
	}
	if err := serviceConfig.Init(); err != nil {
		t.Errorf("Error during the config init: %s\n", err.Error())
	}

	proxyMulti, err := factory.New(&endpointMulti)
	if err != nil {
		t.Errorf("The factory returned an unexpected error: %s\n", err.Error())
	}

	response, err := proxyMulti(context.Background(), &request)
	if err != nil {
		t.Errorf("The proxy middleware propagated an unexpected error: %s\n", err.Error())
	}
	if !response.IsComplete || len(response.Data) != len(expectedResponse.Data) {
		t.Errorf("The proxy middleware propagated an unexpected error: %v\n", response)
	}

	proxySingle, err := factory.New(&endpointSingle)
	if err != nil {
		t.Errorf("The factory returned an unexpected error: %s\n", err.Error())
	}

	response, err = proxySingle(context.Background(), &request)
	if err != nil {
		t.Errorf("The proxy middleware propagated an unexpected error: %s\n", err.Error())
	}
	if !response.IsComplete || len(response.Data) != len(expectedResponse.Data) {
		t.Errorf("The proxy middleware propagated an unexpected error: %v\n", response)
	}
}


================================================
FILE: proxy/formatter.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"context"
	"fmt"
	"strings"

	"github.com/krakend/flatmap/tree"
	"github.com/luraproject/lura/v2/config"
	"github.com/luraproject/lura/v2/logging"
)

// EntityFormatter formats the response data
type EntityFormatter interface {
	Format(Response) Response
}

// EntityFormatterFunc holds the formatter function
type EntityFormatterFunc func(Response) Response

// Format implements the EntityFormatter interface
func (e EntityFormatterFunc) Format(entity Response) Response { return e(entity) }

type propertyFilter func(*Response)

type entityFormatter struct {
	Target         string
	Prefix         string
	PropertyFilter propertyFilter
	Mapping        map[string]string
}

// NewEntityFormatter creates an entity formatter with the received backend definition
func NewEntityFormatter(remote *config.Backend) EntityFormatter {
	if ef := newFlatmapFormatter(remote.ExtraConfig, remote.Target, remote.Group); ef != nil {
		return ef
	}

	var propertyFilter propertyFilter
	if len(remote.AllowList) > 0 {
		propertyFilter = newAllowlistingFilter(remote.AllowList)
	} else {
		propertyFilter = newDenylistingFilter(remote.DenyList)
	}
	sanitizedMappings := make(map[string]string, len(remote.Mapping))
	for i, m := range remote.Mapping {
		v := strings.Split(m, ".")
		sanitizedMappings[i] = v[0]
	}
	return entityFormatter{
		Target:         remote.Target,
		Prefix:         remote.Group,
		PropertyFilter: propertyFilter,
		Mapping:        sanitizedMappings,
	}
}

// Format implements the EntityFormatter interface
func (e entityFormatter) Format(entity Response) Response {
	if e.Target != "" {
		extractTarget(e.Target, &entity)
	}
	if len(entity.Data) > 0 {
		e.PropertyFilter(&entity)
	}
	if len(entity.Data) > 0 {
		for formerKey, newKey := range e.Mapping {
			if v, ok := entity.Data[formerKey]; ok {
				entity.Data[newKey] = v
				delete(entity.Data, formerKey)
			}
		}
	}
	if e.Prefix != "" {
		entity.Data = map[string]interface{}{e.Prefix: entity.Data}
	}
	return entity
}

func extractTarget(target string, entity *Response) {
	for _, part := range strings.Split(target, ".") {
		if tmp, ok := entity.Data[part]; ok {
			entity.Data, ok = tmp.(map[string]interface{})
			if !ok {
				entity.Data = map[string]interface{}{}
				return
			}
		} else {
			entity.Data = map[string]interface{}{}
			return
		}
	}
}

func AllowlistPrune(wlDict, inDict map[string]interface{}) bool {
	canDelete := true
	var deleteSibling bool
	for k, v := range inDict {
		deleteSibling = true
		if subWl, ok := wlDict[k]; ok {
			if subWlDict, okk := subWl.(map[string]interface{}); okk {
				if subInDict, isDict := v.(map[string]interface{}); isDict && !AllowlistPrune(subWlDict, subInDict) {
					deleteSibling = false
				}
			} else {
				// Allowlist leaf, maintain this branch
				deleteSibling = false
			}
		}
		if deleteSibling {
			delete(inDict, k)
		} else {
			canDelete = false
		}
	}
	return canDelete
}

func newAllowlistingFilter(Allowlist []string) propertyFilter {
	wlDict := make(map[string]interface{})
	for _, k := range Allowlist {
		wlFields := strings.Split(k, ".")
		d := buildDictPath(wlDict, wlFields[:len(wlFields)-1])
		d[wlFields[len(wlFields)-1]] = true
	}

	return func(entity *Response) {
		if AllowlistPrune(wlDict, entity.Data) {
			for k := range entity.Data {
				delete(entity.Data, k)
			}
		}
	}
}

func buildDictPath(accumulator map[string]interface{}, fields []string) map[string]interface{} {
	var ok bool
	var c map[string]interface{}
	var fIdx int
	fEnd := len(fields)
	p := accumulator
	for fIdx = 0; fIdx < fEnd; fIdx++ {
		if c, ok = p[fields[fIdx]].(map[string]interface{}); !ok {
			break
		}
		p = c
	}
	for ; fIdx < fEnd; fIdx++ {
		c = make(map[string]interface{})
		p[fields[fIdx]] = c
		p = c
	}
	return p
}

func buildDenyTree(path []string, tree map[string]interface{}) {
	if len(path) == 0 {
		return
	}
	n := path[0]
	if len(path) == 1 {
		// this is the node to be deleted, so, any other child
		// that is under this node, does not need to be visited:
		// we "delete" any descendant from this node
		tree[n] = nil
		return
	}

	if k, ok := tree[n]; ok {
		if k == nil {
			// all this child should be deleted, so, no matter
			// if the entry says to delete some extra child..
			// everything will be deleted
			return
		}
		childTree, ok := k.(map[string]interface{})
		if !ok {
			// this should never happen if this algorithm is correct
			tree[n] = nil
			return
		}
		buildDenyTree(path[1:], childTree)
		return
	}

	// it the key does not exist, we need to keep building the children,
	// and at this point we know that path is at least len = 2, and that
	// tree[n] does not exist
	childTree := make(map[string]interface{}, 1)
	tree[n] = childTree
	buildDenyTree(path[1:], childTree)
}

func recDelete(ref map[string]interface{}, v interface{}) {
	m, ok := v.(map[string]interface{})
	if !ok || m == nil {
		return
	}

	for rk, rv := range ref {
		dv, dok := m[rk]
		if !dok {
			continue
		}
		if rv == nil {
			delete(m, rk)
			continue
		}
		recDelete(rv.(map[string]interface{}), dv)
	}
}

func newDenylistingFilter(blacklist []string) propertyFilter {
	bl := make(map[string]interface{}, len(blacklist))
	for _, key := range blacklist {
		keys := strings.Split(key, ".")
		buildDenyTree(keys, bl)
	}

	return func(entity *Response) {
		recDelete(bl, entity.Data)
	}
}

const flatmapKey = "flatmap_filter"

type flatmapFormatter struct {
	Target string
	Prefix string
	Ops    []flatmapOp
}

type flatmapOp struct {
	Type string
	Args [][]string
}

// Format implements the EntityFormatter interface
func (e flatmapFormatter) Format(entity Response) Response {
	if e.Target != "" {
		extractTarget(e.Target, &entity)
	}

	e.processOps(&entity)

	if e.Prefix != "" {
		entity.Data = map[string]interface{}{e.Prefix: entity.Data}
	}
	return entity
}

func (e flatmapFormatter) processOps(entity *Response) {
	flatten, err := tree.New(entity.Data)
	if err != nil {
		return
	}
	for _, op := range e.Ops {
		switch op.Type {
		case "move":
			flatten.Move(op.Args[0], op.Args[1])
		case "append":
			flatten.Append(op.Args[0], op.Args[1])
		case "del":
			for _, k := range op.Args {
				flatten.Del(k)
			}
		default:
		}
	}

	entity.Data, _ = flatten.Get([]string{}).(map[string]interface{})
}

func newFlatmapFormatter(cfg config.ExtraConfig, target, group string) *flatmapFormatter {
	if v, ok := cfg[Namespace]; ok {
		if e, ok := v.(map[string]interface{}); ok {
			if vs, ok := e[flatmapKey].([]interface{}); ok {
				if len(vs) == 0 {
					return nil
				}
				var ops []flatmapOp
				for _, v := range vs {
					m, ok := v.(map[string]interface{})
					if !ok {
						continue
					}
					op := flatmapOp{}
					if t, ok := m["type"].(string); ok {
						op.Type = t
					} else {
						continue
					}
					if args, ok := m["args"].([]interface{}); ok {
						op.Args = make([][]string, len(args))
						for k, arg := range args {
							if t, ok := arg.(string); ok {
								op.Args[k] = strings.Split(t, ".")
							}
						}
					}
					ops = append(ops, op)
				}
				if len(ops) == 0 {
					return nil
				}
				return &flatmapFormatter{
					Target: target,
					Prefix: group,
					Ops:    ops,
				}
			}
		}
	}
	return nil
}

// NewFlatmapMiddleware creates a proxy middleware that enables applying flatmap operations to the proxy response
func NewFlatmapMiddleware(logger logging.Logger, cfg *config.EndpointConfig) Middleware {
	formatter := newFlatmapFormatter(cfg.ExtraConfig, "", "")
	return func(next ...Proxy) Proxy {
		if len(next) > 1 {
			logger.Fatal("too many proxies for this proxy middleware: NewFlatmapMiddleware only accepts 1 proxy, got %d", len(next))
			return nil
		}

		if formatter == nil {
			return next[0]
		}

		logger.Debug(
			fmt.Sprintf(
				"[ENDPOINT: %s][Flatmap] Adding flatmap manipulator with %d operations",
				cfg.Endpoint,
				len(formatter.Ops),
			),
		)

		return func(ctx context.Context, request *Request) (*Response, error) {
			resp, err := next[0](ctx, request)
			if err != nil {
				return resp, err
			}
			r := formatter.Format(*resp)
			return &r, nil
		}
	}
}


================================================
FILE: proxy/formatter_benchmark_test.go
================================================
// SPDX-License-Identifier: Apache-2.0

package proxy

import (
	"bytes"
	"fmt"
	"strconv"
	"testing"

	"github.com/luraproject/lura/v2/config"
)

func BenchmarkEntityFormatter_allowFilter(b *testing.B) {
	data := map[string]interface{}{
		"supu": 42,
		"tupu": false,
		"foo":  "bar",
	}

	for _, extraFields := range []int{0, 5, 10, 15, 20, 25} {
		sampleData := data
		for i := 0; i < extraFields; i++ {
			sampleData[fmt.Sprintf("%d", i)] = i
		}
		for _, testCase := range [][]string{
			{},
			{"supu"},
			{"supu", "tupu"},
			{"supu", "tupu", "foo"},
			{"supu", "tupu", "foo", "unknown"},
		} {
			sample := Response{
				Data:       sampleData,
				IsComplete: true,
			}
			b.Run(fmt.Sprintf("with %d elements with %d extra fields", len(testCase), extraFields), func(b *testing.B) {
				f := NewEntityFormatter(&config.Backend{AllowList: testCase})
				b.ResetTimer()
				b.ReportAllocs()
				for i := 0; i < b.N; i++ {
					f.Format(sample)
				}
			})
		}
	}

}

func benchmarkDeepChilds(depth, extraSiblings int) map[string]interface{} {
	data := make(map[string]interface{}, extraSiblings+1)
	for i := 0; i < extraSiblings; i++ {
		data[fmt.Sprintf("extra%d", i)] = "sibling_value"
	}
	if depth > 0 {
		data[fmt.Sprintf("child%d", depth)] = benchmarkDeepChilds(depth-1, extraSiblings)
	} else {
		data["child0"] = 1
	}
	return data
}

func benchmarkDeepStructure(numTargets, targetDepth, extraFields, extraSiblings int) (map[string]interface{}, []string) {
	data := make(map[string]interface{}, numTargets+extraFields)
	targetKeys := make([]string, numTargets)
	for i := 0; i < numTargets; i++ {
		data[fmt.Sprintf("target%d", i)] = benchmarkDeepChilds(targetDepth-1, extraSiblings)
	}
	for j := 0; j < extraFields; j++ {
		data[fmt.Sprintf("extra%d", j)] = benchmarkDeepChilds(targetDepth-1, extraSiblings)
	}
	// create the target list
	for i := 0; i < numTargets; i++ {
		var buffer bytes.Buffer
		buffer.WriteString(fmt.Sprintf("target%d", i))
		for j := targetDepth - 1; j >= 0; j-- {
			buffer.WriteString(fmt.Sprintf(".child%d", j))
		}
		targetKeys[i] = buffer.String()
	}
	return data, targetKeys
}

func BenchmarkEntityFormatter_deepAllowFilter(b *testing.B) {
	numTargets := []int{0, 1, 2, 5, 10}
	depths := []int{1, 3, 7}
	for _, nTargets := range numTargets {
		for _, depth := range depths {
			extraFields := nTargets + depth*2
			extraSiblings := nTargets
			data, allow := benchmarkDeepStructure(nTargets, depth, extraFields, extraSiblings)
			sample := Response{
				Data:       data,
				IsComplete: true,
			}
			f := NewEntityFormatter(&config.Backend{AllowList: allow})
			b.Run(fmt.Sprintf("numTargets:%d,depth:%d,extraFields:%d,extraSiblings:%d", nTargets, depth, extraFields, extraSiblings), func(b *testing.B) {
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					f.Format(sample)
				}
			})
		}
	}
}

func BenchmarkEntityFormatter_denyFilter(b *testing.B) {
	data := map[string]interface{}{
		"supu": 42,
		"tupu": false,
		"foo":  "bar",
	}

	for _, extraFields := range []int{0, 5, 10, 15, 20, 25} {
		sampleData := data
		for i := 0; i < extraFields; i++ {
			sampleData[fmt.Sprintf("%d", i)] = i
		}
		for _, testCase := range [][]string{
			{},
			{"supu"},
			{"supu", "tupu"},
			{"supu", "tupu", "foo"},
			{"supu", "tupu", "foo", "unknown"},
		} {
Download .txt
gitextract_fat13st9/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── feature_request.md
│   │   └── report-vulnerability.md
│   ├── label-commenter-config.yml
│   └── workflows/
│       ├── go.yml
│       ├── labels.yml
│       └── lock-threads.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── async/
│   ├── asyncagent.go
│   └── asyncagent_test.go
├── backoff/
│   ├── backoff.go
│   └── backoff_test.go
├── config/
│   ├── config.go
│   ├── config_test.go
│   ├── parser.go
│   ├── parser_test.go
│   ├── uri.go
│   └── uri_test.go
├── core/
│   └── version.go
├── docs/
│   ├── BENCHMARKS.md
│   ├── CONFIG.md
│   ├── OVERVIEW.md
│   └── README.md
├── encoding/
│   ├── encoding.go
│   ├── encoding_test.go
│   ├── json_benchmark_test.go
│   ├── json_test.go
│   └── register.go
├── go.mod
├── go.sum
├── logging/
│   ├── log.go
│   └── log_test.go
├── plugin/
│   ├── plugin.go
│   └── plugin_test.go
├── proxy/
│   ├── balancing.go
│   ├── balancing_benchmark_test.go
│   ├── balancing_test.go
│   ├── concurrent.go
│   ├── concurrent_benchmark_test.go
│   ├── concurrent_test.go
│   ├── factory.go
│   ├── factory_test.go
│   ├── formatter.go
│   ├── formatter_benchmark_test.go
│   ├── formatter_test.go
│   ├── graphql.go
│   ├── graphql_test.go
│   ├── headers_filter.go
│   ├── headers_filter_test.go
│   ├── http.go
│   ├── http_benchmark_test.go
│   ├── http_response.go
│   ├── http_response_test.go
│   ├── http_test.go
│   ├── logging.go
│   ├── logging_test.go
│   ├── merging.go
│   ├── merging_benchmark_test.go
│   ├── merging_test.go
│   ├── plugin/
│   │   ├── modifier.go
│   │   ├── modifier_test.go
│   │   └── tests/
│   │       ├── error/
│   │       │   └── main.go
│   │       └── logger/
│   │           └── main.go
│   ├── plugin.go
│   ├── plugin_test.go
│   ├── proxy.go
│   ├── proxy_test.go
│   ├── query_strings_filter.go
│   ├── query_strings_filter_test.go
│   ├── register.go
│   ├── register_test.go
│   ├── request.go
│   ├── request_benchmark_test.go
│   ├── request_test.go
│   ├── shadow.go
│   ├── shadow_test.go
│   ├── stack_benchmark_test.go
│   ├── stack_test.go
│   ├── static.go
│   └── static_test.go
├── register/
│   ├── register.go
│   └── register_test.go
├── router/
│   ├── chi/
│   │   ├── endpoint.go
│   │   ├── endpoint_benchmark_test.go
│   │   ├── endpoint_test.go
│   │   ├── router.go
│   │   └── router_test.go
│   ├── gin/
│   │   ├── debug.go
│   │   ├── debug_test.go
│   │   ├── echo.go
│   │   ├── echo_test.go
│   │   ├── endpoint.go
│   │   ├── endpoint_benchmark_test.go
│   │   ├── endpoint_test.go
│   │   ├── engine.go
│   │   ├── engine_test.go
│   │   ├── render.go
│   │   ├── render_test.go
│   │   ├── router.go
│   │   ├── router_test.go
│   │   └── safecast.go
│   ├── gorilla/
│   │   ├── router.go
│   │   └── router_test.go
│   ├── helper.go
│   ├── helper_test.go
│   ├── httptreemux/
│   │   ├── router.go
│   │   └── router_test.go
│   ├── mux/
│   │   ├── debug.go
│   │   ├── debug_test.go
│   │   ├── echo.go
│   │   ├── echo_test.go
│   │   ├── endpoint.go
│   │   ├── endpoint_benchmark_test.go
│   │   ├── endpoint_test.go
│   │   ├── engine.go
│   │   ├── engine_test.go
│   │   ├── render.go
│   │   ├── render_test.go
│   │   ├── router.go
│   │   └── router_test.go
│   ├── negroni/
│   │   ├── router.go
│   │   └── router_test.go
│   └── router.go
├── sd/
│   ├── dnssrv/
│   │   ├── subscriber.go
│   │   └── subscriber_test.go
│   ├── loadbalancing.go
│   ├── loadbalancing_benchmark_test.go
│   ├── loadbalancing_test.go
│   ├── register.go
│   ├── register_test.go
│   └── subscriber.go
├── test/
│   ├── doc.go
│   └── integration_test.go
└── transport/
    └── http/
        ├── client/
        │   ├── executor.go
        │   ├── executor_test.go
        │   ├── graphql/
        │   │   ├── graphql.go
        │   │   └── graphql_test.go
        │   ├── plugin/
        │   │   ├── doc.go
        │   │   ├── executor.go
        │   │   ├── plugin.go
        │   │   ├── plugin_test.go
        │   │   └── tests/
        │   │       └── main.go
        │   ├── status.go
        │   └── status_test.go
        └── server/
            ├── plugin/
            │   ├── doc.go
            │   ├── plugin.go
            │   ├── plugin_test.go
            │   ├── server.go
            │   └── tests/
            │       └── main.go
            ├── server.go
            ├── server_test.go
            └── tls_test.go
Download .txt
SYMBOL INDEX (1134 symbols across 134 files)

FILE: async/asyncagent.go
  type Options (line 22) | type Options struct
  type Factory (line 41) | type Factory
  type AgentStarter (line 44) | type AgentStarter
    method Start (line 47) | func (a AgentStarter) Start(

FILE: async/asyncagent_test.go
  function TestAgentStarter_Start_last (line 14) | func TestAgentStarter_Start_last(t *testing.T) {
  function TestAgentStarter_Start_first (line 50) | func TestAgentStarter_Start_first(t *testing.T) {

FILE: backoff/backoff.go
  function GetByName (line 15) | func GetByName(strategy string) TimeToWaitBeforeRetry {
  type TimeToWaitBeforeRetry (line 31) | type TimeToWaitBeforeRetry
  function DefaultBackoff (line 37) | func DefaultBackoff(_ int) time.Duration {
  function ExponentialBackoff (line 42) | func ExponentialBackoff(i int) time.Duration {
  function ExponentialJitterBackoff (line 48) | func ExponentialJitterBackoff(i int) time.Duration {
  function LinearBackoff (line 53) | func LinearBackoff(i int) time.Duration {
  function LinearJitterBackoff (line 59) | func LinearJitterBackoff(i int) time.Duration {
  function init (line 65) | func init() {
  function jitter (line 70) | func jitter(i int) time.Duration {

FILE: backoff/backoff_test.go
  function TestExponentialBackoff (line 10) | func TestExponentialBackoff(t *testing.T) {
  function TestLinearBackoff (line 20) | func TestLinearBackoff(t *testing.T) {
  function TestDefaultBackoff (line 28) | func TestDefaultBackoff(t *testing.T) {

FILE: config/config.go
  constant BracketsRouterPatternBuilder (line 29) | BracketsRouterPatternBuilder = iota
  constant ColonRouterPatternBuilder (line 31) | ColonRouterPatternBuilder
  constant DefaultMaxIdleConnsPerHost (line 33) | DefaultMaxIdleConnsPerHost = 250
  constant DefaultTimeout (line 35) | DefaultTimeout = 2 * time.Second
  constant ConfigVersion (line 38) | ConfigVersion = 3
  type ServiceConfig (line 45) | type ServiceConfig struct
    method Hash (line 393) | func (s *ServiceConfig) Hash() (string, error) {
    method Init (line 409) | func (s *ServiceConfig) Init() error {
    method Normalize (line 430) | func (s *ServiceConfig) Normalize() {
    method initGlobalParams (line 446) | func (s *ServiceConfig) initGlobalParams() error {
    method initAsyncAgents (line 473) | func (s *ServiceConfig) initAsyncAgents() error {
    method initEndpoints (line 501) | func (s *ServiceConfig) initEndpoints() error {
    method paramExtractionPattern (line 547) | func (s *ServiceConfig) paramExtractionPattern() *regexp.Regexp {
    method extractPlaceHoldersFromURLTemplate (line 554) | func (*ServiceConfig) extractPlaceHoldersFromURLTemplate(subject strin...
    method initEndpointDefaults (line 563) | func (s *ServiceConfig) initEndpointDefaults(e int) {
    method initAsyncAgentDefaults (line 586) | func (s *ServiceConfig) initAsyncAgentDefaults(e int) {
    method initBackendDefaults (line 599) | func (s *ServiceConfig) initBackendDefaults(e, b int) error {
    method initBackendURLMappings (line 630) | func (s *ServiceConfig) initBackendURLMappings(e, b int, inputParams m...
  type AsyncAgent (line 200) | type AsyncAgent struct
  type Consumer (line 213) | type Consumer struct
  type Connection (line 221) | type Connection struct
  type EndpointConfig (line 229) | type EndpointConfig struct
    method validate (line 700) | func (e *EndpointConfig) validate() error {
  type Backend (line 254) | type Backend struct
  type Plugin (line 309) | type Plugin struct
  type TLSKeyPair (line 315) | type TLSKeyPair struct
  type TLS (line 321) | type TLS struct
  type ClientTLS (line 337) | type ClientTLS struct
  type ClientTLSCert (line 350) | type ClientTLSCert struct
  type ExtraConfig (line 356) | type ExtraConfig
    method sanitize (line 358) | func (e *ExtraConfig) sanitize() {
    method Normalize (line 370) | func (e *ExtraConfig) Normalize() {
  function fromSetToSortedSlice (line 671) | func fromSetToSortedSlice(set map[string]interface{}) []string {
  function uniqueOutput (line 680) | func uniqueOutput(output []string) ([]string, int) {
  type EndpointMatchError (line 721) | type EndpointMatchError struct
    method Error (line 728) | func (e *EndpointMatchError) Error() string {
  type NoBackendsError (line 734) | type NoBackendsError struct
    method Error (line 740) | func (n *NoBackendsError) Error() string {
  type UnsupportedVersionError (line 746) | type UnsupportedVersionError struct
    method Error (line 752) | func (u *UnsupportedVersionError) Error() string {
  type EndpointPathError (line 758) | type EndpointPathError struct
    method Error (line 764) | func (e *EndpointPathError) Error() string {
  type UndefinedOutputParamError (line 770) | type UndefinedOutputParamError struct
    method Error (line 780) | func (u *UndefinedOutputParamError) Error() string {
  type WrongNumberOfParamsError (line 794) | type WrongNumberOfParamsError struct
    method Error (line 803) | func (w *WrongNumberOfParamsError) Error() string {
  function SetSequentialParamsPattern (line 814) | func SetSequentialParamsPattern(pattern string) error {
  function SetInvalidPattern (line 824) | func SetInvalidPattern(pattern string) {
  function validateAddress (line 828) | func validateAddress(address string) bool {

FILE: config/config_test.go
  function TestConfig_rejectInvalidVersion (line 13) | func TestConfig_rejectInvalidVersion(t *testing.T) {
  function TestConfig_rejectInvalidEndpoints (line 21) | func TestConfig_rejectInvalidEndpoints(t *testing.T) {
  function TestConfig_initBackendURLMappings_ok (line 38) | func TestConfig_initBackendURLMappings_ok(t *testing.T) {
  function TestConfig_initBackendURLMappings_tooManyOutput (line 86) | func TestConfig_initBackendURLMappings_tooManyOutput(t *testing.T) {
  function TestConfig_initBackendURLMappings_undefinedOutput (line 107) | func TestConfig_initBackendURLMappings_undefinedOutput(t *testing.T) {
  function TestConfig_init (line 125) | func TestConfig_init(t *testing.T) {
  function TestConfig_initKONoBackends (line 219) | func TestConfig_initKONoBackends(t *testing.T) {
  function TestConfig_initKOMultipleBackendsForNoopEncoder (line 238) | func TestConfig_initKOMultipleBackendsForNoopEncoder(t *testing.T) {
  function TestConfig_initKOInvalidHost (line 264) | func TestConfig_initKOInvalidHost(t *testing.T) {
  function TestConfig_initKOInvalidDebugPattern (line 289) | func TestConfig_initKOInvalidDebugPattern(t *testing.T) {
  function TestConfig_initKOValidSetinvalidPattern (line 313) | func TestConfig_initKOValidSetinvalidPattern(t *testing.T) {

FILE: config/parser.go
  type Parser (line 13) | type Parser interface
  type ParserFunc (line 19) | type ParserFunc
    method Parse (line 22) | func (f ParserFunc) Parse(configFile string) (ServiceConfig, error) { ...
  function NewParser (line 25) | func NewParser() Parser {
  function NewParserWithFileReader (line 30) | func NewParserWithFileReader(f FileReaderFunc) Parser {
  type parser (line 34) | type parser struct
    method Parse (line 39) | func (p parser) Parse(configFile string) (ServiceConfig, error) {
  function CheckErr (line 59) | func CheckErr(err error, configFile string) error {
  function NewParseError (line 78) | func NewParseError(err error, configFile string, offset int) *ParseError {
  function getErrorRowCol (line 90) | func getErrorRowCol(source []byte, offset int) (row, col int) {
  type ParseError (line 111) | type ParseError struct
    method Error (line 120) | func (p *ParseError) Error() string {
  type FileReaderFunc (line 132) | type FileReaderFunc
  type parseableServiceConfig (line 134) | type parseableServiceConfig struct
    method normalize (line 172) | func (p *parseableServiceConfig) normalize() ServiceConfig {
  type parseableTLSKeyPair (line 254) | type parseableTLSKeyPair struct
  type parseableTLS (line 259) | type parseableTLS struct
  type parseableClientTLS (line 274) | type parseableClientTLS struct
  type parseableClientTLSCert (line 285) | type parseableClientTLSCert struct
  type parseableEndpointConfig (line 290) | type parseableEndpointConfig struct
    method normalize (line 303) | func (p *parseableEndpointConfig) normalize() *EndpointConfig {
  type parseableAsyncAgent (line 325) | type parseableAsyncAgent struct
    method normalize (line 343) | func (p *parseableAsyncAgent) normalize() *AsyncAgent {
  type parseableBackend (line 370) | type parseableBackend struct
    method normalize (line 389) | func (p *parseableBackend) normalize() *Backend {
  function parseDuration (line 416) | func parseDuration(v string) time.Duration {

FILE: config/parser_test.go
  function TestNewParser_ok (line 10) | func TestNewParser_ok(t *testing.T) {
  function TestNewParser_errorMessages (line 159) | func TestNewParser_errorMessages(t *testing.T) {
  function testExtraConfig (line 246) | func testExtraConfig(extraConfig map[string]interface{}, t *testing.T) {
  function TestNewParser_unknownFile (line 260) | func TestNewParser_unknownFile(t *testing.T) {
  function TestNewParser_readingError (line 267) | func TestNewParser_readingError(t *testing.T) {
  function TestNewParser_initError (line 284) | func TestNewParser_initError(t *testing.T) {
  function TestParserFunc (line 300) | func TestParserFunc(t *testing.T) {

FILE: config/uri.go
  type URIParser (line 17) | type URIParser interface
  type SafeURIParser (line 26) | type SafeURIParser interface
  function NewURIParser (line 34) | func NewURIParser() URIParser {
  function NewSafeURIParser (line 39) | func NewSafeURIParser() URI {
  type URI (line 44) | type URI
    method SafeCleanHosts (line 47) | func (u URI) SafeCleanHosts(hosts []string) ([]string, error) {
    method CleanHosts (line 61) | func (u URI) CleanHosts(hosts []string) []string {
    method SafeCleanHost (line 70) | func (URI) SafeCleanHost(host string) (string, error) {
    method CleanHost (line 84) | func (u URI) CleanHost(host string) string {
    method CleanPath (line 93) | func (URI) CleanPath(path string) string {
    method GetEndpointPath (line 98) | func (u URI) GetEndpointPath(path string, params []string) string {

FILE: config/uri_test.go
  function TestURIParser_cleanHosts (line 7) | func TestURIParser_cleanHosts(t *testing.T) {
  function TestURIParser_cleanPath (line 34) | func TestURIParser_cleanPath(t *testing.T) {
  function TestURIParser_getEndpointPath (line 66) | func TestURIParser_getEndpointPath(t *testing.T) {
  function TestURIParser_getEndpointPath_notStrictREST (line 93) | func TestURIParser_getEndpointPath_notStrictREST(t *testing.T) {

FILE: core/version.go
  constant KrakendHeaderName (line 15) | KrakendHeaderName = "X-KRAKEND"

FILE: encoding/encoding.go
  type Decoder (line 22) | type Decoder
  type DecoderFactory (line 25) | type DecoderFactory
  constant NOOP (line 28) | NOOP = "no-op"
  function NoOpDecoder (line 31) | func NoOpDecoder(_ io.Reader, _ *map[string]interface{}) error { return ...
  function noOpDecoderFactory (line 33) | func noOpDecoderFactory(_ bool) func(io.Reader, *map[string]interface{})...
  constant JSON (line 36) | JSON = "json"
  function NewJSONDecoder (line 39) | func NewJSONDecoder(isCollection bool) func(io.Reader, *map[string]inter...
  function JSONDecoder (line 47) | func JSONDecoder(r io.Reader, v *map[string]interface{}) error {
  function JSONCollectionDecoder (line 54) | func JSONCollectionDecoder(r io.Reader, v *map[string]interface{}) error {
  constant SAFE_JSON (line 66) | SAFE_JSON = "safejson"
  function NewSafeJSONDecoder (line 69) | func NewSafeJSONDecoder(_ bool) func(io.Reader, *map[string]interface{})...
  function SafeJSONDecoder (line 74) | func SafeJSONDecoder(r io.Reader, v *map[string]interface{}) error {
  constant STRING (line 93) | STRING = "string"
  function NewStringDecoder (line 96) | func NewStringDecoder(_ bool) func(io.Reader, *map[string]interface{}) e...
  function StringDecoder (line 101) | func StringDecoder(r io.Reader, v *map[string]interface{}) error {

FILE: encoding/encoding_test.go
  function TestNoOpDecoder (line 14) | func TestNoOpDecoder(t *testing.T) {
  function TestRegister (line 30) | func TestRegister(t *testing.T) {
  function TestGet (line 48) | func TestGet(t *testing.T) {
  function TestRegister_complete_ok (line 70) | func TestRegister_complete_ok(t *testing.T) {
  function TestRegister_complete_ko (line 107) | func TestRegister_complete_ko(t *testing.T) {
  function checkDecoder (line 144) | func checkDecoder(t *testing.T, name string) {
  type erroredReader (line 157) | type erroredReader
    method Error (line 159) | func (e erroredReader) Error() string {
    method Read (line 163) | func (e erroredReader) Read(_ []byte) (n int, err error) {

FILE: encoding/json_benchmark_test.go
  function BenchmarkDecoder (line 11) | func BenchmarkDecoder(b *testing.B) {

FILE: encoding/json_test.go
  function ExampleNewJSONDecoder_map (line 12) | func ExampleNewJSONDecoder_map() {
  function ExampleNewJSONDecoder_collection (line 25) | func ExampleNewJSONDecoder_collection() {
  function TestNewJSONDecoder_map (line 38) | func TestNewJSONDecoder_map(t *testing.T) {
  function TestNewJSONDecoder_collection (line 59) | func TestNewJSONDecoder_collection(t *testing.T) {
  function TestNewJSONDecoder_ko (line 85) | func TestNewJSONDecoder_ko(t *testing.T) {
  function ExampleNewSafeJSONDecoder (line 94) | func ExampleNewSafeJSONDecoder() {
  function TestNewSafeJSONDecoder_map (line 112) | func TestNewSafeJSONDecoder_map(t *testing.T) {
  function TestNewSafeJSONDecoder_collection (line 133) | func TestNewSafeJSONDecoder_collection(t *testing.T) {
  function TestNewSafeJSONDecoder_other (line 159) | func TestNewSafeJSONDecoder_other(t *testing.T) {

FILE: encoding/register.go
  function GetRegister (line 12) | func GetRegister() *DecoderRegister {
  type untypedRegister (line 16) | type untypedRegister interface
  type DecoderRegister (line 23) | type DecoderRegister struct
    method Register (line 28) | func (r *DecoderRegister) Register(name string, dec func(bool) func(io...
    method Get (line 34) | func (r *DecoderRegister) Get(name string) func(bool) func(io.Reader, ...
  function initDecoderRegister (line 55) | func initDecoderRegister() *DecoderRegister {

FILE: logging/log.go
  type Logger (line 17) | type Logger interface
  constant LEVEL_DEBUG (line 28) | LEVEL_DEBUG = iota
  constant LEVEL_INFO (line 30) | LEVEL_INFO
  constant LEVEL_WARNING (line 32) | LEVEL_WARNING
  constant LEVEL_ERROR (line 34) | LEVEL_ERROR
  constant LEVEL_CRITICAL (line 36) | LEVEL_CRITICAL
  function NewLogger (line 55) | func NewLogger(level string, out io.Writer, prefix string) (BasicLogger,...
  type BasicLogger (line 63) | type BasicLogger struct
    method Debug (line 70) | func (l BasicLogger) Debug(v ...interface{}) {
    method Info (line 78) | func (l BasicLogger) Info(v ...interface{}) {
    method Warning (line 86) | func (l BasicLogger) Warning(v ...interface{}) {
    method Error (line 94) | func (l BasicLogger) Error(v ...interface{}) {
    method Critical (line 102) | func (l BasicLogger) Critical(v ...interface{}) {
    method Fatal (line 107) | func (l BasicLogger) Fatal(v ...interface{}) {
    method prependLog (line 112) | func (l BasicLogger) prependLog(level string, v ...interface{}) {

FILE: logging/log_test.go
  constant debugMsg (line 14) | debugMsg    = "Debug msg"
  constant infoMsg (line 15) | infoMsg     = "Info msg"
  constant warningMsg (line 16) | warningMsg  = "Warning msg"
  constant errorMsg (line 17) | errorMsg    = "Error msg"
  constant criticalMsg (line 18) | criticalMsg = "Critical msg"
  constant fatalMsg (line 19) | fatalMsg    = "Fatal msg"
  function TestNewLogger (line 22) | func TestNewLogger(t *testing.T) {
  function TestNewLogger_unknownLevel (line 42) | func TestNewLogger_unknownLevel(t *testing.T) {
  function TestNewLogger_fatal (line 53) | func TestNewLogger_fatal(t *testing.T) {
  function logSomeStuff (line 72) | func logSomeStuff(level string) string {

FILE: plugin/plugin.go
  function Scan (line 15) | func Scan(folder, pattern string) ([]string, error) {

FILE: plugin/plugin_test.go
  function TestScan_ok (line 10) | func TestScan_ok(t *testing.T) {
  function TestScan_noFolder (line 34) | func TestScan_noFolder(t *testing.T) {
  function TestScan_emptyFolder (line 49) | func TestScan_emptyFolder(t *testing.T) {
  function TestScan_noMatches (line 65) | func TestScan_noMatches(t *testing.T) {

FILE: proxy/balancing.go
  function NewLoadBalancedMiddleware (line 16) | func NewLoadBalancedMiddleware(remote *config.Backend) Middleware {
  function NewLoadBalancedMiddlewareWithSubscriber (line 22) | func NewLoadBalancedMiddlewareWithSubscriber(subscriber sd.Subscriber) M...
  function NewRoundRobinLoadBalancedMiddleware (line 28) | func NewRoundRobinLoadBalancedMiddleware(remote *config.Backend) Middlew...
  function NewRandomLoadBalancedMiddleware (line 34) | func NewRandomLoadBalancedMiddleware(remote *config.Backend) Middleware {
  function NewRoundRobinLoadBalancedMiddlewareWithSubscriber (line 40) | func NewRoundRobinLoadBalancedMiddlewareWithSubscriber(subscriber sd.Sub...
  function NewRandomLoadBalancedMiddlewareWithSubscriber (line 46) | func NewRandomLoadBalancedMiddlewareWithSubscriber(subscriber sd.Subscri...
  function NewLoadBalancedMiddlewareWithLogger (line 52) | func NewLoadBalancedMiddlewareWithLogger(l logging.Logger, remote *confi...
  function NewLoadBalancedMiddlewareWithSubscriberAndLogger (line 58) | func NewLoadBalancedMiddlewareWithSubscriberAndLogger(l logging.Logger, ...
  function NewRoundRobinLoadBalancedMiddlewareWithLogger (line 64) | func NewRoundRobinLoadBalancedMiddlewareWithLogger(l logging.Logger, rem...
  function NewRandomLoadBalancedMiddlewareWithLogger (line 70) | func NewRandomLoadBalancedMiddlewareWithLogger(l logging.Logger, remote ...
  function NewRoundRobinLoadBalancedMiddlewareWithSubscriberAndLogger (line 76) | func NewRoundRobinLoadBalancedMiddlewareWithSubscriberAndLogger(l loggin...
  function NewRandomLoadBalancedMiddlewareWithSubscriberAndLogger (line 82) | func NewRandomLoadBalancedMiddlewareWithSubscriberAndLogger(l logging.Lo...
  function newLoadBalancedMiddleware (line 86) | func newLoadBalancedMiddleware(l logging.Logger, lb sd.Balancer) Middlew...

FILE: proxy/balancing_benchmark_test.go
  constant veryLargeString (line 13) | veryLargeString = "abcdefghijklmopqrstuvwxyzabcdefghijklmopqrstuvwxyzabc...
  function BenchmarkNewLoadBalancedMiddleware (line 15) | func BenchmarkNewLoadBalancedMiddleware(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel3 (line 30) | func BenchmarkNewLoadBalancedMiddleware_parallel3(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel5 (line 34) | func BenchmarkNewLoadBalancedMiddleware_parallel5(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel9 (line 38) | func BenchmarkNewLoadBalancedMiddleware_parallel9(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel13 (line 42) | func BenchmarkNewLoadBalancedMiddleware_parallel13(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel17 (line 46) | func BenchmarkNewLoadBalancedMiddleware_parallel17(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel21 (line 50) | func BenchmarkNewLoadBalancedMiddleware_parallel21(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel25 (line 54) | func BenchmarkNewLoadBalancedMiddleware_parallel25(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel50 (line 58) | func BenchmarkNewLoadBalancedMiddleware_parallel50(b *testing.B) {
  function BenchmarkNewLoadBalancedMiddleware_parallel100 (line 62) | func BenchmarkNewLoadBalancedMiddleware_parallel100(b *testing.B) {
  function benchmarkNewLoadBalancedMiddleware_parallel (line 66) | func benchmarkNewLoadBalancedMiddleware_parallel(b *testing.B, subject s...

FILE: proxy/balancing_test.go
  function TestNewLoadBalancedMiddleware_ok (line 17) | func TestNewLoadBalancedMiddleware_ok(t *testing.T) {
  function TestNewLoadBalancedMiddleware_explosiveBalancer (line 33) | func TestNewLoadBalancedMiddleware_explosiveBalancer(t *testing.T) {
  function TestNewRoundRobinLoadBalancedMiddleware (line 41) | func TestNewRoundRobinLoadBalancedMiddleware(t *testing.T) {
  function TestNewRandomLoadBalancedMiddleware (line 47) | func TestNewRandomLoadBalancedMiddleware(t *testing.T) {
  function testLoadBalancedMw (line 53) | func testLoadBalancedMw(t *testing.T, lb Middleware) {
  function TestNewLoadBalancedMiddleware_parsingError (line 93) | func TestNewLoadBalancedMiddleware_parsingError(t *testing.T) {
  function TestNewRoundRobinLoadBalancedMiddleware_DNSSRV (line 108) | func TestNewRoundRobinLoadBalancedMiddleware_DNSSRV(t *testing.T) {
  type dummyBalancer (line 125) | type dummyBalancer
    method Host (line 127) | func (d dummyBalancer) Host() (string, error) { return string(d), nil }
  type explosiveBalancer (line 129) | type explosiveBalancer struct
    method Host (line 133) | func (e explosiveBalancer) Host() (string, error) { return "", e.Error }

FILE: proxy/concurrent.go
  function NewConcurrentMiddlewareWithLogger (line 16) | func NewConcurrentMiddlewareWithLogger(logger logging.Logger, remote *co...
  function NewConcurrentMiddleware (line 67) | func NewConcurrentMiddleware(remote *config.Backend) Middleware {
  function processConcurrentCall (line 73) | func processConcurrentCall(ctx context.Context, next Proxy, request *Req...

FILE: proxy/concurrent_benchmark_test.go
  function BenchmarkNewConcurrentMiddleware_singleNext (line 13) | func BenchmarkNewConcurrentMiddleware_singleNext(b *testing.B) {

FILE: proxy/concurrent_test.go
  function TestNewConcurrentMiddleware_ok (line 14) | func TestNewConcurrentMiddleware_ok(t *testing.T) {
  function TestNewConcurrentMiddleware_okAfterKo (line 54) | func TestNewConcurrentMiddleware_okAfterKo(t *testing.T) {
  function TestNewConcurrentMiddleware_timeout (line 104) | func TestNewConcurrentMiddleware_timeout(t *testing.T) {

FILE: proxy/factory.go
  type Factory (line 16) | type Factory interface
  type FactoryFunc (line 22) | type FactoryFunc
    method New (line 25) | func (f FactoryFunc) New(cfg *config.EndpointConfig) (Proxy, error) { ...
  function DefaultFactory (line 28) | func DefaultFactory(logger logging.Logger) Factory {
  function DefaultFactoryWithSubscriber (line 33) | func DefaultFactoryWithSubscriber(logger logging.Logger, sF sd.Subscribe...
  function NewDefaultFactory (line 38) | func NewDefaultFactory(backendFactory BackendFactory, logger logging.Log...
  function NewDefaultFactoryWithSubscriber (line 47) | func NewDefaultFactoryWithSubscriber(backendFactory BackendFactory, logg...
  type defaultFactory (line 51) | type defaultFactory struct
    method New (line 58) | func (pf defaultFactory) New(cfg *config.EndpointConfig) (p Proxy, err...
    method newMulti (line 76) | func (pf defaultFactory) newMulti(cfg *config.EndpointConfig) (p Proxy...
    method newSingle (line 86) | func (pf defaultFactory) newSingle(cfg *config.EndpointConfig) (Proxy,...
    method newStack (line 90) | func (pf defaultFactory) newStack(backend *config.Backend) (p Proxy) {

FILE: proxy/factory_test.go
  function TestFactoryFunc (line 21) | func TestFactoryFunc(t *testing.T) {
  function TestDefaultFactoryWithSubscriber (line 36) | func TestDefaultFactoryWithSubscriber(t *testing.T) {
  function TestDefaultFactory_noBackends (line 51) | func TestDefaultFactory_noBackends(t *testing.T) {
  function TestNewDefaultFactory_ok (line 65) | func TestNewDefaultFactory_ok(t *testing.T) {

FILE: proxy/formatter.go
  type EntityFormatter (line 16) | type EntityFormatter interface
  type EntityFormatterFunc (line 21) | type EntityFormatterFunc
    method Format (line 24) | func (e EntityFormatterFunc) Format(entity Response) Response { return...
  type propertyFilter (line 26) | type propertyFilter
  type entityFormatter (line 28) | type entityFormatter struct
    method Format (line 61) | func (e entityFormatter) Format(entity Response) Response {
  function NewEntityFormatter (line 36) | func NewEntityFormatter(remote *config.Backend) EntityFormatter {
  function extractTarget (line 82) | func extractTarget(target string, entity *Response) {
  function AllowlistPrune (line 97) | func AllowlistPrune(wlDict, inDict map[string]interface{}) bool {
  function newAllowlistingFilter (line 121) | func newAllowlistingFilter(Allowlist []string) propertyFilter {
  function buildDictPath (line 138) | func buildDictPath(accumulator map[string]interface{}, fields []string) ...
  function buildDenyTree (line 158) | func buildDenyTree(path []string, tree map[string]interface{}) {
  function recDelete (line 196) | func recDelete(ref map[string]interface{}, v interface{}) {
  function newDenylistingFilter (line 215) | func newDenylistingFilter(blacklist []string) propertyFilter {
  constant flatmapKey (line 227) | flatmapKey = "flatmap_filter"
  type flatmapFormatter (line 229) | type flatmapFormatter struct
    method Format (line 241) | func (e flatmapFormatter) Format(entity Response) Response {
    method processOps (line 254) | func (e flatmapFormatter) processOps(entity *Response) {
  type flatmapOp (line 235) | type flatmapOp struct
  function newFlatmapFormatter (line 276) | func newFlatmapFormatter(cfg config.ExtraConfig, target, group string) *...
  function NewFlatmapMiddleware (line 320) | func NewFlatmapMiddleware(logger logging.Logger, cfg *config.EndpointCon...

FILE: proxy/formatter_benchmark_test.go
  function BenchmarkEntityFormatter_allowFilter (line 14) | func BenchmarkEntityFormatter_allowFilter(b *testing.B) {
  function benchmarkDeepChilds (line 50) | func benchmarkDeepChilds(depth, extraSiblings int) map[string]interface{} {
  function benchmarkDeepStructure (line 63) | func benchmarkDeepStructure(numTargets, targetDepth, extraFields, extraS...
  function BenchmarkEntityFormatter_deepAllowFilter (line 84) | func BenchmarkEntityFormatter_deepAllowFilter(b *testing.B) {
  function BenchmarkEntityFormatter_denyFilter (line 108) | func BenchmarkEntityFormatter_denyFilter(b *testing.B) {
  function BenchmarkEntityFormatter_grouping (line 143) | func BenchmarkEntityFormatter_grouping(b *testing.B) {
  function BenchmarkEntityFormatter_mapping (line 165) | func BenchmarkEntityFormatter_mapping(b *testing.B) {
  function BenchmarkEntityFormatter_flatmapAlt (line 195) | func BenchmarkEntityFormatter_flatmapAlt(b *testing.B) {
  function BenchmarkEntityFormatter_flatmap (line 277) | func BenchmarkEntityFormatter_flatmap(b *testing.B) {

FILE: proxy/formatter_test.go
  function TestEntityFormatterFunc (line 14) | func TestEntityFormatterFunc(t *testing.T) {
  function TestEntityFormatter_newAllowFilter (line 27) | func TestEntityFormatter_newAllowFilter(t *testing.T) {
  function TestEntityFormatter_newAllowDeepFields (line 75) | func TestEntityFormatter_newAllowDeepFields(t *testing.T) {
  function TestEntityFormatter_newDenyFilter (line 128) | func TestEntityFormatter_newDenyFilter(t *testing.T) {
  function TestEntityFormatter_grouping (line 216) | func TestEntityFormatter_grouping(t *testing.T) {
  function TestEntityFormatter_mapping (line 252) | func TestEntityFormatter_mapping(t *testing.T) {
  function TestEntityFormatter_targeting (line 304) | func TestEntityFormatter_targeting(t *testing.T) {
  function TestEntityFormatter_targetingNested (line 336) | func TestEntityFormatter_targetingNested(t *testing.T) {
  function TestEntityFormatter_targetingUnknownFields (line 370) | func TestEntityFormatter_targetingUnknownFields(t *testing.T) {
  function TestEntityFormatter_targetingNonObjects (line 387) | func TestEntityFormatter_targetingNonObjects(t *testing.T) {
  function TestEntityFormatter_altogether (line 405) | func TestEntityFormatter_altogether(t *testing.T) {
  function TestEntityFormatter_flatmap (line 450) | func TestEntityFormatter_flatmap(t *testing.T) {
  function TestNewFlatmapMiddleware (line 543) | func TestNewFlatmapMiddleware(t *testing.T) {

FILE: proxy/graphql.go
  function NewGraphQLMiddleware (line 26) | func NewGraphQLMiddleware(logger logging.Logger, remote *config.Backend)...

FILE: proxy/graphql_test.go
  function TestNewGraphQLMiddleware_mutation (line 18) | func TestNewGraphQLMiddleware_mutation(t *testing.T) {
  function TestNewGraphQLMiddleware_query (line 74) | func TestNewGraphQLMiddleware_query(t *testing.T) {

FILE: proxy/headers_filter.go
  function NewFilterHeadersMiddleware (line 14) | func NewFilterHeadersMiddleware(logger logging.Logger, remote *config.Ba...

FILE: proxy/headers_filter_test.go
  function TestNewFilterHeadersMiddleware (line 13) | func TestNewFilterHeadersMiddleware(t *testing.T) {
  function TestNewFilterHeadersMiddlewareBlockAll (line 85) | func TestNewFilterHeadersMiddlewareBlockAll(t *testing.T) {
  function TestNewFilterHeadersMiddlewareAllowAll (line 121) | func TestNewFilterHeadersMiddlewareAllowAll(t *testing.T) {

FILE: proxy/http.go
  function HTTPProxyFactory (line 23) | func HTTPProxyFactory(client *http.Client) BackendFactory {
  function CustomHTTPProxyFactory (line 28) | func CustomHTTPProxyFactory(cf client.HTTPClientFactory) BackendFactory {
  function NewHTTPProxy (line 35) | func NewHTTPProxy(remote *config.Backend, cf client.HTTPClientFactory, d...
  function NewHTTPProxyWithHTTPExecutor (line 40) | func NewHTTPProxyWithHTTPExecutor(remote *config.Backend, re client.HTTP...
  constant clientHTTPOptions (line 51) | clientHTTPOptions            string = "backend/http/client"
  constant clientHTTPOptionRedirectPost (line 52) | clientHTTPOptionRedirectPost string = "send_body_on_redirect"
  function redirectPostReaderFactory (line 61) | func redirectPostReaderFactory(cfg *config.Backend) func(r io.ReadCloser...
  function NewHTTPProxyDetailed (line 86) | func NewHTTPProxyDetailed(cfg *config.Backend, re client.HTTPRequestExec...
  function NewRequestBuilderMiddlewareWithLogger (line 144) | func NewRequestBuilderMiddlewareWithLogger(logger logging.Logger, remote...
  function newRequestBuilderMiddleware (line 148) | func newRequestBuilderMiddleware(l logging.Logger, remote *config.Backen...
  type responseError (line 162) | type responseError interface

FILE: proxy/http_benchmark_test.go
  function BenchmarkNewRequestBuilderMiddleware (line 12) | func BenchmarkNewRequestBuilderMiddleware(b *testing.B) {

FILE: proxy/http_response.go
  type HTTPResponseParser (line 15) | type HTTPResponseParser
  type HTTPResponseParserConfig (line 24) | type HTTPResponseParserConfig struct
  type HTTPResponseParserFactory (line 30) | type HTTPResponseParserFactory
  function DefaultHTTPResponseParserFactory (line 33) | func DefaultHTTPResponseParserFactory(cfg HTTPResponseParserConfig) HTTP...
  function NoOpHTTPResponseParser (line 63) | func NoOpHTTPResponseParser(ctx context.Context, resp *http.Response) (*...

FILE: proxy/http_response_test.go
  function TestNopHTTPResponseParser (line 16) | func TestNopHTTPResponseParser(t *testing.T) {
  function TestDefaultHTTPResponseParser_gzipped (line 51) | func TestDefaultHTTPResponseParser_gzipped(t *testing.T) {
  function TestDefaultHTTPResponseParser_gzipped_bad_header (line 87) | func TestDefaultHTTPResponseParser_gzipped_bad_header(t *testing.T) {
  function TestDefaultHTTPResponseParser_plain (line 124) | func TestDefaultHTTPResponseParser_plain(t *testing.T) {

FILE: proxy/http_test.go
  function TestNewHTTPProxy_ok (line 25) | func TestNewHTTPProxy_ok(t *testing.T) {
  function TestNewHTTPProxy_cancel (line 100) | func TestNewHTTPProxy_cancel(t *testing.T) {
  function TestNewHTTPProxy_badResponseBody (line 137) | func TestNewHTTPProxy_badResponseBody(t *testing.T) {
  function TestNewHTTPProxy_badStatusCode (line 172) | func TestNewHTTPProxy_badStatusCode(t *testing.T) {
  function TestNewHTTPProxy_badStatusCode_detailed (line 207) | func TestNewHTTPProxy_badStatusCode_detailed(t *testing.T) {
  function TestNewHTTPProxy_decodingError (line 255) | func TestNewHTTPProxy_decodingError(t *testing.T) {
  function TestNewHTTPProxy_badMethod (line 292) | func TestNewHTTPProxy_badMethod(t *testing.T) {
  function TestNewHTTPProxy_requestKo (line 331) | func TestNewHTTPProxy_requestKo(t *testing.T) {
  function TestNewRequestBuilderMiddleware_ok (line 374) | func TestNewRequestBuilderMiddleware_ok(t *testing.T) {
  function TestDefaultHTTPResponseParserConfig_nopDecoder (line 405) | func TestDefaultHTTPResponseParserConfig_nopDecoder(t *testing.T) {
  function TestDefaultHTTPResponseParserConfig_nopEntityFormatter (line 415) | func TestDefaultHTTPResponseParserConfig_nopEntityFormatter(t *testing.T) {
  function TestNewHTTPProxy_noopDecoder (line 430) | func TestNewHTTPProxy_noopDecoder(t *testing.T) {
  function TestNewHTTPProxy_redirectWithBody (line 493) | func TestNewHTTPProxy_redirectWithBody(t *testing.T) {

FILE: proxy/logging.go
  function NewLoggingMiddleware (line 14) | func NewLoggingMiddleware(logger logging.Logger, name string) Middleware {

FILE: proxy/logging_test.go
  function TestNewLoggingMiddleware_ok (line 15) | func TestNewLoggingMiddleware_ok(t *testing.T) {
  function TestNewLoggingMiddleware_erroredResponse (line 48) | func TestNewLoggingMiddleware_erroredResponse(t *testing.T) {
  function TestNewLoggingMiddleware_nullResponse (line 90) | func TestNewLoggingMiddleware_nullResponse(t *testing.T) {

FILE: proxy/merging.go
  function NewMergeDataMiddleware (line 20) | func NewMergeDataMiddleware(logger logging.Logger, endpointConfig *confi...
  type BackendFiltererFactory (line 77) | type BackendFiltererFactory
  type BackendFilterer (line 82) | type BackendFilterer
  function defaultBackendFiltererFactory (line 84) | func defaultBackendFiltererFactory(_ *config.EndpointConfig) ([]BackendF...
  type backendFiltererRegistry (line 88) | type backendFiltererRegistry struct
  function RegisterBackendFiltererFactory (line 102) | func RegisterBackendFiltererFactory(logPrefix string, f BackendFiltererF...
  function ResetBackendFiltererFactory (line 107) | func ResetBackendFiltererFactory() {
  type sequentialBackendReplacement (line 112) | type sequentialBackendReplacement struct
  function sequentialMergerConfig (line 119) | func sequentialMergerConfig(cfg *config.EndpointConfig) (bool, [][]seque...
  function hasUnsafeBackends (line 190) | func hasUnsafeBackends(cfg *config.EndpointConfig) bool {
  function parallelMerge (line 204) | func parallelMerge(
  function sequentialMerge (line 244) | func sequentialMerge( // skipcq: GO-R1005
  type incrementalMergeAccumulator (line 372) | type incrementalMergeAccumulator struct
    method Merge (line 387) | func (i *incrementalMergeAccumulator) Merge(res *Response, err error) {
    method Result (line 407) | func (i *incrementalMergeAccumulator) Result() (*Response, error) {
  function newIncrementalMergeAccumulator (line 379) | func newIncrementalMergeAccumulator(total int, combiner ResponseCombiner...
  function requestPart (line 418) | func requestPart(ctx context.Context, next Proxy, request *Request, out ...
  function sequentialRequestPart (line 440) | func sequentialRequestPart(ctx context.Context, next Proxy, request *Req...
  function newMergeError (line 462) | func newMergeError(errs []error) error {
  type mergeError (line 469) | type mergeError struct
    method Error (line 473) | func (m mergeError) Error() string {
    method Errors (line 481) | func (m mergeError) Errors() []error {
  type ResponseCombiner (line 486) | type ResponseCombiner
  function RegisterResponseCombiner (line 489) | func RegisterResponseCombiner(name string, f ResponseCombiner) {
  constant mergeKey (line 494) | mergeKey               = "combiner"
  constant isSequentialKey (line 495) | isSequentialKey        = "sequential"
  constant sequentialPropagateKey (line 496) | sequentialPropagateKey = "sequential_propagated_params"
  constant defaultCombinerName (line 497) | defaultCombinerName    = "default"
  function initResponseCombiners (line 502) | func initResponseCombiners() *combinerRegister {
  function getResponseCombinerName (line 506) | func getResponseCombinerName(extra config.ExtraConfig) string {
  function getResponseCombiner (line 519) | func getResponseCombiner(extra config.ExtraConfig) ResponseCombiner {
  function combineData (line 525) | func combineData(total int, parts []*Response) *Response {

FILE: proxy/merging_benchmark_test.go
  function BenchmarkNewMergeDataMiddleware (line 15) | func BenchmarkNewMergeDataMiddleware(b *testing.B) {
  function BenchmarkNewMergeDataMiddleware_sequential (line 51) | func BenchmarkNewMergeDataMiddleware_sequential(b *testing.B) {

FILE: proxy/merging_test.go
  function TestNewMergeDataMiddleware (line 17) | func TestNewMergeDataMiddleware(t *testing.T) {
  function testNewMergeDataMiddleware_empty (line 41) | func testNewMergeDataMiddleware_empty(t *testing.T) {
  function testNewMergeDataMiddleware_ok (line 84) | func testNewMergeDataMiddleware_ok(t *testing.T) {
  function testNewMergeDataMiddleware_sequential (line 117) | func testNewMergeDataMiddleware_sequential(t *testing.T) {
  function checkRequestParam (line 247) | func checkRequestParam(t *testing.T, r *Request, k, v string) {
  function testNewMergeDataMiddleware_sequential_unavailableParams (line 253) | func testNewMergeDataMiddleware_sequential_unavailableParams(t *testing....
  function testNewMergeDataMiddleware_sequential_erroredBackend (line 312) | func testNewMergeDataMiddleware_sequential_erroredBackend(t *testing.T) {
  function testNewMergeDataMiddleware_sequential_erroredFirstBackend (line 364) | func testNewMergeDataMiddleware_sequential_erroredFirstBackend(t *testin...
  function testNewMergeDataMiddleware_mergeIncompleteResults (line 411) | func testNewMergeDataMiddleware_mergeIncompleteResults(t *testing.T) {
  function testNewMergeDataMiddleware_mergeEmptyResults (line 444) | func testNewMergeDataMiddleware_mergeEmptyResults(t *testing.T) {
  function testNewMergeDataMiddleware_partialTimeout (line 477) | func testNewMergeDataMiddleware_partialTimeout(t *testing.T) {
  function testNewMergeDataMiddleware_partial (line 510) | func testNewMergeDataMiddleware_partial(t *testing.T) {
  function testNewMergeDataMiddleware_nullResponse (line 543) | func testNewMergeDataMiddleware_nullResponse(t *testing.T) {
  function testNewMergeDataMiddleware_timeout (line 581) | func testNewMergeDataMiddleware_timeout(t *testing.T) {
  function testRegisterResponseCombiner (line 622) | func testRegisterResponseCombiner(t *testing.T) {
  function Test_incrementalMergeAccumulator_invalidResponse (line 670) | func Test_incrementalMergeAccumulator_invalidResponse(t *testing.T) {
  function Test_incrementalMergeAccumulator_incompleteResponse (line 703) | func Test_incrementalMergeAccumulator_incompleteResponse(t *testing.T) {
  function testNewMergeDataMiddleware_simpleFiltering (line 722) | func testNewMergeDataMiddleware_simpleFiltering(t *testing.T) {
  function testNewMergeDataMiddleware_sequentialFiltering (line 767) | func testNewMergeDataMiddleware_sequentialFiltering(t *testing.T) {

FILE: proxy/plugin.go
  function NewPluginMiddleware (line 20) | func NewPluginMiddleware(logger logging.Logger, endpoint *config.Endpoin...
  function NewBackendPluginMiddleware (line 34) | func NewBackendPluginMiddleware(logger logging.Logger, remote *config.Ba...
  function newPluginMiddleware (line 45) | func newPluginMiddleware(logger logging.Logger, tag, pattern string, cfg...
  function executeRequestModifiers (line 137) | func executeRequestModifiers(ctx context.Context, reqModifiers []func(in...
  function executeResponseModifiers (line 164) | func executeResponseModifiers(ctx context.Context, respModifiers []func(...
  type RequestWrapper (line 200) | type RequestWrapper interface
  type ResponseWrapper (line 211) | type ResponseWrapper interface
  function newRequestWrapper (line 219) | func newRequestWrapper(ctx context.Context, r *Request) *requestWrapper {
  type requestWrapper (line 232) | type requestWrapper struct
    method Context (line 243) | func (r *requestWrapper) Context() context.Context     { return r.ctx }
    method Method (line 244) | func (r *requestWrapper) Method() string               { return r.meth...
    method URL (line 245) | func (r *requestWrapper) URL() *url.URL                { return r.url }
    method Query (line 246) | func (r *requestWrapper) Query() url.Values            { return r.query }
    method Path (line 247) | func (r *requestWrapper) Path() string                 { return r.path }
    method Body (line 248) | func (r *requestWrapper) Body() io.ReadCloser          { return r.body }
    method Params (line 249) | func (r *requestWrapper) Params() map[string]string    { return r.para...
    method Headers (line 250) | func (r *requestWrapper) Headers() map[string][]string { return r.head...
  type metadataWrapper (line 252) | type metadataWrapper struct
    method Headers (line 257) | func (m metadataWrapper) Headers() map[string][]string { return m.head...
    method StatusCode (line 258) | func (m metadataWrapper) StatusCode() int              { return m.stat...
  type responseWrapper (line 260) | type responseWrapper struct
    method Context (line 269) | func (r responseWrapper) Context() context.Context     { return r.ctx }
    method Request (line 270) | func (r responseWrapper) Request() interface{}         { return r.requ...
    method Data (line 271) | func (r responseWrapper) Data() map[string]interface{} { return r.data }
    method IsComplete (line 272) | func (r responseWrapper) IsComplete() bool             { return r.isCo...
    method Io (line 273) | func (r responseWrapper) Io() io.Reader                { return r.io }
    method Headers (line 274) | func (r responseWrapper) Headers() map[string][]string { return r.meta...
    method StatusCode (line 275) | func (r responseWrapper) StatusCode() int              { return r.meta...

FILE: proxy/plugin/modifier.go
  constant Namespace (line 21) | Namespace = "github.com/devopsfaith/krakend/proxy/plugin"
  constant requestNamespace (line 23) | requestNamespace = "github.com/devopsfaith/krakend/proxy/plugin/request"
  constant responseNamespace (line 25) | responseNamespace = "github.com/devopsfaith/krakend/proxy/plugin/response"
  type ModifierFactory (line 31) | type ModifierFactory
  function GetRequestModifier (line 34) | func GetRequestModifier(name string) (ModifierFactory, bool) {
  function GetResponseModifier (line 39) | func GetResponseModifier(name string) (ModifierFactory, bool) {
  function getModifier (line 43) | func getModifier(namespace, name string) (ModifierFactory, bool) {
  function RegisterModifier (line 60) | func RegisterModifier(
  type Registerer (line 75) | type Registerer interface
  type LoggerRegisterer (line 84) | type LoggerRegisterer interface
  type ContextRegisterer (line 88) | type ContextRegisterer interface
  type RegisterModifierFunc (line 93) | type RegisterModifierFunc
  function Load (line 101) | func Load(path, pattern string, rmf RegisterModifierFunc) (int, error) {
  function LoadWithLogger (line 106) | func LoadWithLogger(path, pattern string, rmf RegisterModifierFunc, logg...
  function LoadWithLoggerAndContext (line 110) | func LoadWithLoggerAndContext(ctx context.Context, path, pattern string,...
  function load (line 118) | func load(ctx context.Context, plugins []string, rmf RegisterModifierFun...
  function open (line 136) | func open(ctx context.Context, pluginName string, rmf RegisterModifierFu...
  type Plugin (line 181) | type Plugin interface
  function defaultPluginOpener (line 188) | func defaultPluginOpener(name string) (Plugin, error) {
  type loaderError (line 192) | type loaderError struct
    method Error (line 197) | func (l loaderError) Error() string {
    method Len (line 205) | func (l loaderError) Len() int {
    method Errs (line 209) | func (l loaderError) Errs() []error {

FILE: proxy/plugin/modifier_test.go
  function ExampleLoadWithLoggerAndContext (line 20) | func ExampleLoadWithLoggerAndContext() {
  function TestLoad (line 118) | func TestLoad(t *testing.T) {
  type RequestWrapper (line 155) | type RequestWrapper interface
  type requestWrapper (line 165) | type requestWrapper struct
    method Context (line 176) | func (r requestWrapper) Context() context.Context     { return r.ctx }
    method Method (line 177) | func (r requestWrapper) Method() string               { return r.method }
    method URL (line 178) | func (r requestWrapper) URL() *url.URL                { return r.url }
    method Query (line 179) | func (r requestWrapper) Query() url.Values            { return r.query }
    method Path (line 180) | func (r requestWrapper) Path() string                 { return r.path }
    method Body (line 181) | func (r requestWrapper) Body() io.ReadCloser          { return r.body }
    method Params (line 182) | func (r requestWrapper) Params() map[string]string    { return r.params }
    method Headers (line 183) | func (r requestWrapper) Headers() map[string][]string { return r.heade...
  type metadataWrapper (line 185) | type metadataWrapper struct
    method Headers (line 190) | func (m metadataWrapper) Headers() map[string][]string { return m.head...
    method StatusCode (line 191) | func (m metadataWrapper) StatusCode() int              { return m.stat...
  type responseWrapper (line 193) | type responseWrapper struct
    method Context (line 202) | func (r responseWrapper) Context() context.Context     { return r.ctx }
    method Request (line 203) | func (r responseWrapper) Request() interface{}         { return r.requ...
    method Data (line 204) | func (r responseWrapper) Data() map[string]interface{} { return r.data }
    method IsComplete (line 205) | func (r responseWrapper) IsComplete() bool             { return r.isCo...
    method Io (line 206) | func (r responseWrapper) Io() io.Reader                { return r.io }
    method Headers (line 207) | func (r responseWrapper) Headers() map[string][]string { return r.meta...
    method StatusCode (line 208) | func (r responseWrapper) StatusCode() int              { return r.meta...

FILE: proxy/plugin/tests/error/main.go
  function main (line 11) | func main() {}
  type registerer (line 17) | type registerer
    method RegisterModifiers (line 19) | func (r registerer) RegisterModifiers(f func(
    method RegisterLogger (line 29) | func (registerer) RegisterLogger(in interface{}) {
    method requestModifierFactory (line 38) | func (registerer) requestModifierFactory(_ map[string]interface{}) fun...
    method reqsponseModifierFactory (line 46) | func (registerer) reqsponseModifierFactory(_ map[string]interface{}) f...
  type customError (line 54) | type customError struct
    method StatusCode (line 59) | func (r customError) StatusCode() int { return r.statusCode }
  type Logger (line 72) | type Logger interface

FILE: proxy/plugin/tests/logger/main.go
  function main (line 14) | func main() {}
  type registerer (line 21) | type registerer
    method RegisterModifiers (line 23) | func (r registerer) RegisterModifiers(f func(
    method RegisterLogger (line 33) | func (registerer) RegisterLogger(in interface{}) {
    method RegisterContext (line 42) | func (registerer) RegisterContext(c context.Context) {
    method requestModifierFactory (line 47) | func (registerer) requestModifierFactory(_ map[string]interface{}) fun...
    method reqsponseModifierFactory (line 91) | func (registerer) reqsponseModifierFactory(_ map[string]interface{}) f...
  function modifier (line 159) | func modifier(req RequestWrapper) requestWrapper {
  type ResponseWrapper (line 173) | type ResponseWrapper interface
  type RequestWrapper (line 182) | type RequestWrapper interface
  type requestWrapper (line 193) | type requestWrapper struct
    method Context (line 204) | func (r requestWrapper) Context() context.Context     { return r.ctx }
    method Method (line 205) | func (r requestWrapper) Method() string               { return r.method }
    method URL (line 206) | func (r requestWrapper) URL() *url.URL                { return r.url }
    method Query (line 207) | func (r requestWrapper) Query() url.Values            { return r.query }
    method Path (line 208) | func (r requestWrapper) Path() string                 { return r.path }
    method Body (line 209) | func (r requestWrapper) Body() io.ReadCloser          { return r.body }
    method Params (line 210) | func (r requestWrapper) Params() map[string]string    { return r.params }
    method Headers (line 211) | func (r requestWrapper) Headers() map[string][]string { return r.heade...
  type Logger (line 213) | type Logger interface

FILE: proxy/plugin_test.go
  function TestNewPluginMiddleware_logger (line 19) | func TestNewPluginMiddleware_logger(t *testing.T) {
  function TestNewPluginMiddleware_error_request (line 76) | func TestNewPluginMiddleware_error_request(t *testing.T) {
  function TestNewPluginMiddleware_error_response (line 130) | func TestNewPluginMiddleware_error_response(t *testing.T) {
  function TestNewPluginMiddleware_PoisonedPlugin (line 196) | func TestNewPluginMiddleware_PoisonedPlugin(t *testing.T) {
  type statusCodeError (line 244) | type statusCodeError interface

FILE: proxy/proxy.go
  constant Namespace (line 18) | Namespace = "github.com/devopsfaith/krakend/proxy"
  type Metadata (line 21) | type Metadata struct
  type Response (line 27) | type Response struct
  type readCloserWrapper (line 35) | type readCloserWrapper struct
    method Read (line 47) | func (w readCloserWrapper) Read(b []byte) (int, error) {
    method closeOnCancel (line 52) | func (w readCloserWrapper) closeOnCancel() {
  function NewReadCloserWrapper (line 41) | func NewReadCloserWrapper(ctx context.Context, in io.ReadCloser) io.Read...
  type Proxy (line 69) | type Proxy
  type BackendFactory (line 72) | type BackendFactory
  type Middleware (line 82) | type Middleware
  function EmptyMiddlewareWithLogger (line 85) | func EmptyMiddlewareWithLogger(logger logging.Logger, next ...Proxy) Pro...
  function EmptyMiddleware (line 93) | func EmptyMiddleware(next ...Proxy) Proxy {
  function emptyMiddlewareFallback (line 97) | func emptyMiddlewareFallback(logger logging.Logger) Middleware {
  function NoopProxy (line 104) | func NoopProxy(_ context.Context, _ *Request) (*Response, error) { retur...

FILE: proxy/proxy_test.go
  function TestEmptyMiddleware_ok (line 16) | func TestEmptyMiddleware_ok(t *testing.T) {
  function explosiveProxy (line 27) | func explosiveProxy(t *testing.T) Proxy {
  function dummyProxy (line 34) | func dummyProxy(r *Response) Proxy {
  function delayedProxy (line 40) | func delayedProxy(_ *testing.T, timeout time.Duration, r *Response) Proxy {
  function newDummyReadCloser (line 51) | func newDummyReadCloser(content string) io.ReadCloser {
  type dummyReadCloser (line 55) | type dummyReadCloser struct
    method Read (line 59) | func (d dummyReadCloser) Read(p []byte) (int, error) {
    method Close (line 63) | func (dummyReadCloser) Close() error {
  function TestWrapper (line 67) | func TestWrapper(t *testing.T) {
  type dummyRC (line 105) | type dummyRC struct
    method Read (line 111) | func (d *dummyRC) Read(b []byte) (int, error) {
    method Close (line 121) | func (d *dummyRC) Close() error {
    method IsClosed (line 129) | func (d *dummyRC) IsClosed() bool {

FILE: proxy/query_strings_filter.go
  function NewFilterQueryStringsMiddleware (line 15) | func NewFilterQueryStringsMiddleware(logger logging.Logger, remote *conf...

FILE: proxy/query_strings_filter_test.go
  function TestNewFilterQueryStringsMiddleware (line 13) | func TestNewFilterQueryStringsMiddleware(t *testing.T) {
  function TestFilterQueryStringsBlockAll (line 100) | func TestFilterQueryStringsBlockAll(t *testing.T) {
  function TestFilterQueryStringsAllowAll (line 138) | func TestFilterQueryStringsAllowAll(t *testing.T) {

FILE: proxy/register.go
  function NewRegister (line 9) | func NewRegister() *Register {
  type Register (line 15) | type Register struct
  type combinerRegister (line 19) | type combinerRegister struct
    method GetResponseCombiner (line 32) | func (r *combinerRegister) GetResponseCombiner(name string) (ResponseC...
    method SetResponseCombiner (line 43) | func (r *combinerRegister) SetResponseCombiner(name string, rc Respons...
  function newCombinerRegister (line 24) | func newCombinerRegister(data map[string]ResponseCombiner, fallback Resp...

FILE: proxy/register_test.go
  function TestNewRegister_responseCombiner_ok (line 10) | func TestNewRegister_responseCombiner_ok(t *testing.T) {
  function TestNewRegister_responseCombiner_fallbackIfErrored (line 43) | func TestNewRegister_responseCombiner_fallbackIfErrored(t *testing.T) {
  function TestNewRegister_responseCombiner_fallbackIfUnknown (line 68) | func TestNewRegister_responseCombiner_fallbackIfUnknown(t *testing.T) {

FILE: proxy/request.go
  type Request (line 12) | type Request struct
    method GeneratePath (line 23) | func (r *Request) GeneratePath(URLPattern string) {
    method Clone (line 45) | func (r *Request) Clone() Request {
  function CloneRequest (line 63) | func CloneRequest(r *Request) *Request {
  function CloneRequestHeaders (line 81) | func CloneRequestHeaders(headers map[string][]string) map[string][]string {
  function CloneRequestParams (line 92) | func CloneRequestParams(params map[string]string) map[string]string {

FILE: proxy/request_benchmark_test.go
  function BenchmarkRequestGeneratePath (line 7) | func BenchmarkRequestGeneratePath(b *testing.B) {

FILE: proxy/request_test.go
  function TestRequestGeneratePath (line 12) | func TestRequestGeneratePath(t *testing.T) {
  function TestRequest_Clone (line 35) | func TestRequest_Clone(t *testing.T) {
  function TestCloneRequest (line 90) | func TestCloneRequest(t *testing.T) {

FILE: proxy/shadow.go
  constant shadowKey (line 14) | shadowKey        = "shadow"
  constant shadowTimeoutKey (line 15) | shadowTimeoutKey = "shadow_timeout"
  type shadowFactory (line 18) | type shadowFactory struct
    method New (line 25) | func (s shadowFactory) New(cfg *config.EndpointConfig) (p Proxy, err e...
  function NewShadowFactory (line 60) | func NewShadowFactory(f Factory) Factory {
  function ShadowMiddlewareWithLogger (line 65) | func ShadowMiddlewareWithLogger(logger logging.Logger, next ...Proxy) Pr...
  function ShadowMiddleware (line 81) | func ShadowMiddleware(next ...Proxy) Proxy {
  function ShadowMiddlewareWithTimeoutAndLogger (line 86) | func ShadowMiddlewareWithTimeoutAndLogger(logger logging.Logger, timeout...
  function ShadowMiddlewareWithTimeout (line 102) | func ShadowMiddlewareWithTimeout(timeout time.Duration, next ...Proxy) P...
  function NewShadowProxy (line 108) | func NewShadowProxy(p1, p2 Proxy) Proxy {
  function NewShadowProxyWithTimeout (line 114) | func NewShadowProxyWithTimeout(timeout time.Duration, p1, p2 Proxy) Proxy {
  function isShadowBackend (line 126) | func isShadowBackend(c *config.Backend) (time.Duration, bool) {
  type contextWrapper (line 159) | type contextWrapper struct
    method Value (line 164) | func (c contextWrapper) Value(key interface{}) interface{} {
  function newContextWrapperWithTimeout (line 168) | func newContextWrapperWithTimeout(data context.Context, timeout time.Dur...

FILE: proxy/shadow_test.go
  function newAssertionProxy (line 31) | func newAssertionProxy(counter *uint64) Proxy {
  function TestIsShadowBackend (line 38) | func TestIsShadowBackend(t *testing.T) {
  function TestShadowMiddleware (line 60) | func TestShadowMiddleware(t *testing.T) {
  function TestShadowFactory_noBackends (line 71) | func TestShadowFactory_noBackends(t *testing.T) {
  function TestNewShadowFactory (line 87) | func TestNewShadowFactory(t *testing.T) {
  function TestShadowMiddleware_erroredBackend (line 125) | func TestShadowMiddleware_erroredBackend(t *testing.T) {
  function TestShadowMiddleware_partialTimeout (line 156) | func TestShadowMiddleware_partialTimeout(t *testing.T) {

FILE: proxy/stack_benchmark_test.go
  function BenchmarkProxyStack_single (line 17) | func BenchmarkProxyStack_single(b *testing.B) {
  function BenchmarkProxyStack_multi (line 81) | func BenchmarkProxyStack_multi(b *testing.B) {
  function BenchmarkProxyStack_multipost (line 152) | func BenchmarkProxyStack_multipost(b *testing.B) {
  function BenchmarkProxyStack_single_flatmap (line 233) | func BenchmarkProxyStack_single_flatmap(b *testing.B) {
  function BenchmarkProxyStack_multi_flatmap (line 309) | func BenchmarkProxyStack_multi_flatmap(b *testing.B) {

FILE: proxy/stack_test.go
  function TestProxyStack_multi (line 24) | func TestProxyStack_multi(t *testing.T) {

FILE: proxy/static.go
  function NewStaticMiddleware (line 15) | func NewStaticMiddleware(logger logging.Logger, endpointConfig *config.E...
  constant staticKey (line 59) | staticKey = "static"
  constant staticAlwaysStrategy (line 61) | staticAlwaysStrategy       = "always"
  constant staticIfSuccessStrategy (line 62) | staticIfSuccessStrategy    = "success"
  constant staticIfErroredStrategy (line 63) | staticIfErroredStrategy    = "errored"
  constant staticIfCompleteStrategy (line 64) | staticIfCompleteStrategy   = "complete"
  constant staticIfIncompleteStrategy (line 65) | staticIfIncompleteStrategy = "incomplete"
  type staticConfig (line 68) | type staticConfig struct
  function getStaticMiddlewareCfg (line 74) | func getStaticMiddlewareCfg(extra config.ExtraConfig) (staticConfig, boo...
  function staticAlwaysMatch (line 120) | func staticAlwaysMatch(_ *Response, _ error) bool      { return true }
  function staticIfSuccessMatch (line 121) | func staticIfSuccessMatch(_ *Response, err error) bool { return err == n...
  function staticIfErroredMatch (line 122) | func staticIfErroredMatch(_ *Response, err error) bool { return err != n...
  function staticIfCompleteMatch (line 123) | func staticIfCompleteMatch(r *Response, err error) bool {
  function staticIfIncompleteMatch (line 126) | func staticIfIncompleteMatch(r *Response, _ error) bool { return r == ni...

FILE: proxy/static_test.go
  function TestNewStaticMiddleware_ok (line 15) | func TestNewStaticMiddleware_ok(t *testing.T) {
  type staticMatcherTestCase (line 78) | type staticMatcherTestCase struct
  function TestNewStaticMiddleware (line 85) | func TestNewStaticMiddleware(t *testing.T) {
  function Test_staticAlwaysMatch (line 117) | func Test_staticAlwaysMatch(t *testing.T) {
  function Test_staticIfSuccessMatch (line 161) | func Test_staticIfSuccessMatch(t *testing.T) {
  function Test_staticIfErroredMatch (line 200) | func Test_staticIfErroredMatch(t *testing.T) {
  function Test_staticIfCompleteMatch (line 238) | func Test_staticIfCompleteMatch(t *testing.T) {
  function Test_staticIfIncompleteMatch (line 279) | func Test_staticIfIncompleteMatch(t *testing.T) {
  function testStaticMatcher (line 323) | func testStaticMatcher(t *testing.T, marcher func(*Response, error) bool...
  function Test_getStaticMiddlewareCfg_ko (line 335) | func Test_getStaticMiddlewareCfg_ko(t *testing.T) {
  function Test_getStaticMiddlewareCfg_strategy (line 349) | func Test_getStaticMiddlewareCfg_strategy(t *testing.T) {

FILE: register/register.go
  function New (line 11) | func New() *Namespaced {
  type Namespaced (line 17) | type Namespaced struct
    method Get (line 22) | func (n *Namespaced) Get(namespace string) (*Untyped, bool) {
    method Register (line 32) | func (n *Namespaced) Register(namespace, name string, v interface{}) {
    method AddNamespace (line 45) | func (n *Namespaced) AddNamespace(namespace string) {
  function NewUntyped (line 53) | func NewUntyped() *Untyped {
  type Untyped (line 61) | type Untyped struct
    method Register (line 67) | func (u *Untyped) Register(name string, v interface{}) {
    method Get (line 74) | func (u *Untyped) Get(name string) (interface{}, bool) {
    method Clone (line 82) | func (u *Untyped) Clone() map[string]interface{} {

FILE: register/register_test.go
  function TestNamespaced (line 7) | func TestNamespaced(t *testing.T) {

FILE: router/chi/endpoint.go
  type HandlerFactory (line 17) | type HandlerFactory
  function NewEndpointHandler (line 20) | func NewEndpointHandler(cfg *config.EndpointConfig, prxy proxy.Proxy) ht...
  function extractParamsFromEndpoint (line 27) | func extractParamsFromEndpoint(r *http.Request) map[string]string {

FILE: router/chi/endpoint_benchmark_test.go
  function BenchmarkEndpointHandler_ko (line 20) | func BenchmarkEndpointHandler_ko(b *testing.B) {
  function BenchmarkEndpointHandler_ok (line 43) | func BenchmarkEndpointHandler_ok(b *testing.B) {
  function BenchmarkEndpointHandler_ko_Parallel (line 72) | func BenchmarkEndpointHandler_ko_Parallel(b *testing.B) {
  function BenchmarkEndpointHandler_ok_Parallel (line 97) | func BenchmarkEndpointHandler_ok_Parallel(b *testing.B) {

FILE: router/chi/endpoint_test.go
  function TestEndpointHandler_ok (line 23) | func TestEndpointHandler_ok(t *testing.T) {
  function TestEndpointHandler_okAllParams (line 51) | func TestEndpointHandler_okAllParams(t *testing.T) {
  function TestEndpointHandler_incomplete (line 78) | func TestEndpointHandler_incomplete(t *testing.T) {
  function TestEndpointHandler_errored (line 98) | func TestEndpointHandler_errored(t *testing.T) {
  function TestEndpointHandler_errored_responseError (line 115) | func TestEndpointHandler_errored_responseError(t *testing.T) {
  type dummyResponseError (line 132) | type dummyResponseError struct
    method Error (line 137) | func (d dummyResponseError) Error() string {
    method StatusCode (line 141) | func (d dummyResponseError) StatusCode() int {
  function TestEndpointHandler_incompleteAndErrored (line 145) | func TestEndpointHandler_incompleteAndErrored(t *testing.T) {
  function TestEndpointHandler_cancelEmpty (line 165) | func TestEndpointHandler_cancelEmpty(t *testing.T) {
  function TestEndpointHandler_cancel (line 183) | func TestEndpointHandler_cancel(t *testing.T) {
  function TestEndpointHandler_noop (line 204) | func TestEndpointHandler_noop(t *testing.T) {
  type endpointHandlerTestCase (line 218) | type endpointHandlerTestCase struct
    method test (line 232) | func (tc endpointHandlerTestCase) test(t *testing.T) {
  function startChiServer (line 290) | func startChiServer(handlerFunc http.HandlerFunc) *chi.Mux {

FILE: router/chi/router.go
  constant ChiDefaultDebugPattern (line 24) | ChiDefaultDebugPattern = "/__debug/"
  constant logPrefix (line 26) | logPrefix = "[SERVICE: Chi]"
  type RunServerFunc (line 29) | type RunServerFunc
  type Config (line 32) | type Config struct
  function DefaultFactory (line 44) | func DefaultFactory(proxyFactory proxy.Factory, logger logging.Logger) r...
  function NewFactory (line 59) | func NewFactory(cfg Config) router.Factory {
  type factory (line 66) | type factory struct
    method New (line 71) | func (rf factory) New() router.Router {
    method NewWithContext (line 76) | func (rf factory) NewWithContext(ctx context.Context) router.Router {
  type chiRouter (line 80) | type chiRouter struct
    method Run (line 87) | func (r chiRouter) Run(cfg config.ServiceConfig) {
    method registerDebugEndpoints (line 111) | func (r chiRouter) registerDebugEndpoints() {
    method registerKrakendEndpoints (line 120) | func (r chiRouter) registerKrakendEndpoints(endpoints []*config.Endpoi...
    method registerKrakendEndpoint (line 132) | func (r chiRouter) registerKrakendEndpoint(method string, endpoint *co...

FILE: router/chi/router_test.go
  function TestDefaultFactory_ok (line 27) | func TestDefaultFactory_ok(t *testing.T) {
  function TestDefaultFactory_ko (line 139) | func TestDefaultFactory_ko(t *testing.T) {
  function TestDefaultFactory_proxyFactoryCrash (line 204) | func TestDefaultFactory_proxyFactoryCrash(t *testing.T) {
  function TestRunServer_ko (line 246) | func TestRunServer_ko(t *testing.T) {
  function checkResponseIs404 (line 280) | func checkResponseIs404(t *testing.T, req *http.Request) {
  type noopProxyFactory (line 315) | type noopProxyFactory
    method New (line 317) | func (n noopProxyFactory) New(_ *config.EndpointConfig) (proxy.Proxy, ...
  type erroredProxyFactory (line 326) | type erroredProxyFactory struct
    method New (line 330) | func (e erroredProxyFactory) New(_ *config.EndpointConfig) (proxy.Prox...

FILE: router/gin/debug.go
  function DebugHandler (line 14) | func DebugHandler(logger logging.Logger) gin.HandlerFunc {

FILE: router/gin/debug_test.go
  function TestDebugHandler (line 17) | func TestDebugHandler(t *testing.T) {

FILE: router/gin/echo.go
  type echoResponse (line 12) | type echoResponse struct
  function EchoHandler (line 22) | func EchoHandler() gin.HandlerFunc {

FILE: router/gin/echo_test.go
  function TestEchoHandler (line 15) | func TestEchoHandler(t *testing.T) {
  function echoRunTestRequest (line 53) | func echoRunTestRequest(t *testing.T, e *gin.Engine, body io.Reader, exp...

FILE: router/gin/endpoint.go
  constant requestParamsAsterisk (line 19) | requestParamsAsterisk string = "*"
  type HandlerFactory (line 22) | type HandlerFactory
  function CustomErrorEndpointHandler (line 37) | func CustomErrorEndpointHandler(logger logging.Logger, errF server.ToHTT...
  function NewRequest (line 123) | func NewRequest(headersToSend []string) func(*gin.Context, []string) *pr...
  type encodedResponseError (line 183) | type encodedResponseError interface
  type responseError (line 188) | type responseError interface
  type headerResponseError (line 193) | type headerResponseError interface
  type multiError (line 198) | type multiError interface

FILE: router/gin/endpoint_benchmark_test.go
  function BenchmarkEndpointHandler_ko (line 20) | func BenchmarkEndpointHandler_ko(b *testing.B) {
  function BenchmarkEndpointHandler_ok (line 44) | func BenchmarkEndpointHandler_ok(b *testing.B) {
  function BenchmarkEndpointHandler_ko_Parallel (line 74) | func BenchmarkEndpointHandler_ko_Parallel(b *testing.B) {
  function BenchmarkEndpointHandler_ok_Parallel (line 100) | func BenchmarkEndpointHandler_ok_Parallel(b *testing.B) {

FILE: router/gin/endpoint_test.go
  function TestEndpointHandler_ok (line 26) | func TestEndpointHandler_ok(t *testing.T) {
  function TestEndpointHandler_okAllParams (line 63) | func TestEndpointHandler_okAllParams(t *testing.T) {
  function TestEndpointHandler_incomplete (line 96) | func TestEndpointHandler_incomplete(t *testing.T) {
  function TestEndpointHandler_errored (line 116) | func TestEndpointHandler_errored(t *testing.T) {
  function TestEndpointHandler_errored_responseError (line 133) | func TestEndpointHandler_errored_responseError(t *testing.T) {
  function TestEndpointHandler_errored_withHeaders (line 166) | func TestEndpointHandler_errored_withHeaders(t *testing.T) {
  function TestEndpointHandler_errored_encodedResponseError (line 190) | func TestEndpointHandler_errored_encodedResponseError(t *testing.T) {
  type dummyResponseError (line 210) | type dummyResponseError struct
    method Error (line 215) | func (d dummyResponseError) Error() string {
    method StatusCode (line 219) | func (d dummyResponseError) StatusCode() int {
  type dummyEncodedResponseError (line 223) | type dummyEncodedResponseError struct
    method Encoding (line 228) | func (d dummyEncodedResponseError) Encoding() string {
  type dummyHeadersResponseError (line 232) | type dummyHeadersResponseError struct
    method Headers (line 237) | func (d dummyHeadersResponseError) Headers() map[string][]string {
  function TestEndpointHandler_incompleteAndErrored (line 241) | func TestEndpointHandler_incompleteAndErrored(t *testing.T) {
  function TestEndpointHandler_cancelEmpty (line 261) | func TestEndpointHandler_cancelEmpty(t *testing.T) {
  function TestEndpointHandler_cancel (line 279) | func TestEndpointHandler_cancel(t *testing.T) {
  function TestEndpointHandler_noop (line 300) | func TestEndpointHandler_noop(t *testing.T) {
  function TestCustomErrorEndpointHandler (line 314) | func TestCustomErrorEndpointHandler(t *testing.T) {
  type endpointHandlerTestCase (line 352) | type endpointHandlerTestCase struct
    method test (line 366) | func (tc endpointHandlerTestCase) test(t *testing.T) {
  function startGinServer (line 429) | func startGinServer(handlerFunc gin.HandlerFunc) *gin.Engine {
  function ctxMiddleware (line 437) | func ctxMiddleware(c *gin.Context) {

FILE: router/gin/engine.go
  constant Namespace (line 24) | Namespace = "github_com/luraproject/lura/router/gin"
  type EngineOptions (line 26) | type EngineOptions struct
  function NewEngine (line 34) | func NewEngine(cfg config.ServiceConfig, opt EngineOptions) *gin.Engine {
  function healthEndpoint (line 119) | func healthEndpoint(health <-chan string) func(*gin.Context) {
  function paramChecker (line 139) | func paramChecker() gin.HandlerFunc {
  type engineConfiguration (line 159) | type engineConfiguration struct

FILE: router/gin/engine_test.go
  function TestNewEngine_contextIsPropagated (line 14) | func TestNewEngine_contextIsPropagated(t *testing.T) {
  function TestNewEngine_paramsAreChecked (line 54) | func TestNewEngine_paramsAreChecked(t *testing.T) {

FILE: router/gin/render.go
  type Render (line 18) | type Render
  constant NEGOTIATE (line 21) | NEGOTIATE = "negotiate"
  constant XML (line 22) | XML = "xml"
  constant YAML (line 23) | YAML = "yaml"
  function init (line 37) | func init() {
  function RegisterRender (line 44) | func RegisterRender(name string, r Render) {
  function getRender (line 50) | func getRender(cfg *config.EndpointConfig) Render {
  function getWithFallback (line 63) | func getWithFallback(key string, fallback Render) Render {
  function negotiatedRender (line 73) | func negotiatedRender(c *gin.Context, response *proxy.Response) {
  function stringRender (line 84) | func stringRender(c *gin.Context, response *proxy.Response) {
  function jsonRender (line 104) | func jsonRender(c *gin.Context, response *proxy.Response) {
  function jsonCollectionRender (line 113) | func jsonCollectionRender(c *gin.Context, response *proxy.Response) {
  function xmlRender (line 127) | func xmlRender(c *gin.Context, response *proxy.Response) {
  function yamlRender (line 141) | func yamlRender(c *gin.Context, response *proxy.Response) {
  function noopRender (line 150) | func noopRender(c *gin.Context, response *proxy.Response) {

FILE: router/gin/render_test.go
  function TestRender_Negotiated_ok (line 22) | func TestRender_Negotiated_ok(t *testing.T) {
  function TestRender_Negotiated_noData (line 83) | func TestRender_Negotiated_noData(t *testing.T) {
  function TestRender_Negotiated_noResponse (line 137) | func TestRender_Negotiated_noResponse(t *testing.T) {
  function TestRender_unknown (line 189) | func TestRender_unknown(t *testing.T) {
  function TestRender_string (line 250) | func TestRender_string(t *testing.T) {
  function TestRender_string_noData (line 311) | func TestRender_string_noData(t *testing.T) {
  function TestRegisterRender (line 373) | func TestRegisterRender(t *testing.T) {
  function TestRender_noop (line 398) | func TestRender_noop(t *testing.T) {
  function TestRender_noop_nilBody (line 459) | func TestRender_noop_nilBody(t *testing.T) {
  function TestRender_noop_nilResponse (line 506) | func TestRender_noop_nilResponse(t *testing.T) {

FILE: router/gin/router.go
  constant logPrefix (line 25) | logPrefix = "[SERVICE: Gin]"
  type RunServerFunc (line 28) | type RunServerFunc
  type Config (line 31) | type Config struct
  function DefaultFactory (line 42) | func DefaultFactory(proxyFactory proxy.Factory, logger logging.Logger) r...
  function NewFactory (line 56) | func NewFactory(cfg Config) router.Factory {
  type factory (line 60) | type factory struct
    method New (line 65) | func (rf factory) New() router.Router {
    method NewWithContext (line 70) | func (rf factory) NewWithContext(ctx context.Context) router.Router {
  type ginRouter (line 83) | type ginRouter struct
    method Run (line 97) | func (r ginRouter) Run(cfg config.ServiceConfig) {
    method registerEndpointsAndMiddlewares (line 113) | func (r ginRouter) registerEndpointsAndMiddlewares(cfg config.ServiceC...
    method registerKrakendEndpoints (line 135) | func (r ginRouter) registerKrakendEndpoints(rg *gin.RouterGroup, cfg c...
    method registerKrakendEndpoint (line 152) | func (r ginRouter) registerKrakendEndpoint(rg *gin.RouterGroup, method...
    method registerOptionEndpoints (line 189) | func (r ginRouter) registerOptionEndpoints(rg *gin.RouterGroup) {
  type urlCatalog (line 91) | type urlCatalog struct

FILE: router/gin/router_test.go
  function TestDefaultFactory_ok (line 27) | func TestDefaultFactory_ok(t *testing.T) {
  function TestDefaultFactory_ko (line 148) | func TestDefaultFactory_ko(t *testing.T) {
  function TestDefaultFactory_proxyFactoryCrash (line 207) | func TestDefaultFactory_proxyFactoryCrash(t *testing.T) {
  function TestRunServer_ko (line 274) | func TestRunServer_ko(t *testing.T) {
  function checkResponseIs404 (line 307) | func checkResponseIs404(t *testing.T, req *http.Request) {
  type noopProxyFactory (line 338) | type noopProxyFactory
    method New (line 340) | func (n noopProxyFactory) New(_ *config.EndpointConfig) (proxy.Proxy, ...
  type erroredProxyFactory (line 349) | type erroredProxyFactory struct
    method New (line 353) | func (e erroredProxyFactory) New(_ *config.EndpointConfig) (proxy.Prox...

FILE: router/gin/safecast.go
  type safeCast (line 27) | type safeCast struct
    method Header (line 31) | func (s *safeCast) Header() http.Header {
    method Write (line 35) | func (s *safeCast) Write(b []byte) (int, error) {
    method WriteHeader (line 39) | func (s *safeCast) WriteHeader(statusCode int) {
    method Flush (line 43) | func (s *safeCast) Flush() {
    method Hijack (line 49) | func (s *safeCast) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    method CloseNotify (line 56) | func (s *safeCast) CloseNotify() <-chan bool {
  type safeCaster (line 63) | type safeCaster struct
    method ServeHTTP (line 67) | func (s *safeCaster) ServeHTTP(w http.ResponseWriter, r *http.Request) {

FILE: router/gorilla/router.go
  function DefaultFactory (line 23) | func DefaultFactory(pf proxy.Factory, logger logging.Logger) router.Fact...
  function DefaultConfig (line 28) | func DefaultConfig(pf proxy.Factory, logger logging.Logger) mux.Config {
  function gorillaParamsExtractor (line 41) | func gorillaParamsExtractor(r *http.Request) map[string]string {
  type gorillaEngine (line 50) | type gorillaEngine struct
    method Handle (line 55) | func (g gorillaEngine) Handle(pattern, method string, handler http.Han...
    method ServeHTTP (line 60) | func (g gorillaEngine) ServeHTTP(w http.ResponseWriter, r *http.Reques...

FILE: router/gorilla/router_test.go
  function TestDefaultFactory_ok (line 23) | func TestDefaultFactory_ok(t *testing.T) {
  function TestDefaultFactory_ko (line 127) | func TestDefaultFactory_ko(t *testing.T) {
  function TestDefaultFactory_proxyFactoryCrash (line 185) | func TestDefaultFactory_proxyFactoryCrash(t *testing.T) {
  function checkResponseIs404 (line 228) | func checkResponseIs404(t *testing.T, req *http.Request) {
  type noopProxyFactory (line 262) | type noopProxyFactory
    method New (line 264) | func (n noopProxyFactory) New(_ *config.EndpointConfig) (proxy.Proxy, ...
  type erroredProxyFactory (line 273) | type erroredProxyFactory struct
    method New (line 277) | func (e erroredProxyFactory) New(_ *config.EndpointConfig) (proxy.Prox...
  type identityMiddleware (line 281) | type identityMiddleware struct
    method Handler (line 283) | func (identityMiddleware) Handler(h http.Handler) http.Handler {

FILE: router/helper.go
  function IsValidSequentialEndpoint (line 9) | func IsValidSequentialEndpoint(_ *config.EndpointConfig) bool {

FILE: router/httptreemux/router.go
  function DefaultFactory (line 22) | func DefaultFactory(pf proxy.Factory, logger logging.Logger) router.Fact...
  function DefaultConfig (line 27) | func DefaultConfig(pf proxy.Factory, logger logging.Logger) mux.Config {
  function ParamsExtractor (line 39) | func ParamsExtractor(r *http.Request) map[string]string {
  function NewEngine (line 48) | func NewEngine(m *httptreemux.ContextMux) Engine {
  type Engine (line 52) | type Engine struct
    method Handle (line 57) | func (g Engine) Handle(pattern, method string, handler http.Handler) {
    method ServeHTTP (line 62) | func (g Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {

FILE: router/httptreemux/router_test.go
  function TestDefaultFactory_ok (line 23) | func TestDefaultFactory_ok(t *testing.T) {
  function TestDefaultFactory_ko (line 127) | func TestDefaultFactory_ko(t *testing.T) {
  function TestDefaultFactory_proxyFactoryCrash (line 185) | func TestDefaultFactory_proxyFactoryCrash(t *testing.T) {
  function checkResponseIs404 (line 227) | func checkResponseIs404(t *testing.T, req *http.Request) {
  type noopProxyFactory (line 261) | type noopProxyFactory
    method New (line 263) | func (n noopProxyFactory) New(_ *config.EndpointConfig) (proxy.Proxy, ...
  type erroredProxyFactory (line 272) | type erroredProxyFactory struct
    method New (line 276) | func (e erroredProxyFactory) New(_ *config.EndpointConfig) (proxy.Prox...
  type identityMiddleware (line 280) | type identityMiddleware struct
    method Handler (line 282) | func (identityMiddleware) Handler(h http.Handler) http.Handler {

FILE: router/mux/debug.go
  function DebugHandler (line 14) | func DebugHandler(logger logging.Logger) http.HandlerFunc {

FILE: router/mux/debug_test.go
  function TestDebugHandler (line 15) | func TestDebugHandler(t *testing.T) {

FILE: router/mux/echo.go
  type echoResponse (line 11) | type echoResponse struct
  function EchoHandler (line 21) | func EchoHandler() http.HandlerFunc {

FILE: router/mux/echo_test.go
  function TestEchoHandlerNew (line 13) | func TestEchoHandlerNew(t *testing.T) {
  function echoRunTestRequest (line 48) | func echoRunTestRequest(t *testing.T, e http.HandlerFunc, body io.Reader...

FILE: router/mux/endpoint.go
  constant requestParamsAsterisk (line 19) | requestParamsAsterisk string = "*"
  type HandlerFactory (line 22) | type HandlerFactory
  function CustomEndpointHandler (line 29) | func CustomEndpointHandler(rb RequestBuilder) HandlerFactory {
  function CustomEndpointHandlerWithHTTPError (line 34) | func CustomEndpointHandlerWithHTTPError(rb RequestBuilder, errF server.T...
  type RequestBuilder (line 101) | type RequestBuilder
  type ParamExtractor (line 104) | type ParamExtractor
  function NoopParamExtractor (line 107) | func NoopParamExtractor(_ *http.Request) map[string]string { return map[...
  function NewRequestBuilder (line 115) | func NewRequestBuilder(paramExtractor ParamExtractor) RequestBuilder {
  type responseError (line 167) | type responseError interface
  function clientIP (line 175) | func clientIP(r *http.Request) string {

FILE: router/mux/endpoint_benchmark_test.go
  function BenchmarkEndpointHandler_ko (line 19) | func BenchmarkEndpointHandler_ko(b *testing.B) {
  function BenchmarkEndpointHandler_ok (line 42) | func BenchmarkEndpointHandler_ok(b *testing.B) {
  function BenchmarkEndpointHandler_ko_Parallel (line 71) | func BenchmarkEndpointHandler_ko_Parallel(b *testing.B) {
  function BenchmarkEndpointHandler_ok_Parallel (line 96) | func BenchmarkEndpointHandler_ok_Parallel(b *testing.B) {

FILE: router/mux/endpoint_test.go
  function TestEndpointHandler_ok (line 22) | func TestEndpointHandler_ok(t *testing.T) {
  function TestEndpointHandler_okAllParams (line 46) | func TestEndpointHandler_okAllParams(t *testing.T) {
  function TestEndpointHandler_incomplete (line 73) | func TestEndpointHandler_incomplete(t *testing.T) {
  function TestEndpointHandler_ko (line 93) | func TestEndpointHandler_ko(t *testing.T) {
  function TestEndpointHandler_incompleteAndErrored (line 110) | func TestEndpointHandler_incompleteAndErrored(t *testing.T) {
  function TestEndpointHandler_cancel (line 130) | func TestEndpointHandler_cancel(t *testing.T) {
  function TestEndpointHandler_cancelEmpty (line 151) | func TestEndpointHandler_cancelEmpty(t *testing.T) {
  function TestEndpointHandler_noop (line 169) | func TestEndpointHandler_noop(t *testing.T) {
  function TestEndpointHandler_badMethod (line 183) | func TestEndpointHandler_badMethod(t *testing.T) {
  function TestEndpointHandler_errored_responseError (line 197) | func TestEndpointHandler_errored_responseError(t *testing.T) {
  type dummyResponseError (line 214) | type dummyResponseError struct
    method Error (line 219) | func (d dummyResponseError) Error() string {
    method StatusCode (line 223) | func (d dummyResponseError) StatusCode() int {
  type endpointHandlerTestCase (line 227) | type endpointHandlerTestCase struct
    method test (line 241) | func (tc endpointHandlerTestCase) test(t *testing.T) {
  function startMuxServer (line 299) | func startMuxServer(handlerFunc http.HandlerFunc) *http.ServeMux {

FILE: router/mux/engine.go
  type Engine (line 13) | type Engine interface
  type BasicEngine (line 19) | type BasicEngine struct
    method Handle (line 55) | func (e *BasicEngine) Handle(pattern, method string, handler http.Hand...
    method ServeHTTP (line 65) | func (e *BasicEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    method registrableHandler (line 69) | func (e *BasicEngine) registrableHandler(pattern string) http.Handler {
  function NewHTTPErrorInterceptor (line 25) | func NewHTTPErrorInterceptor(w http.ResponseWriter) *HTTPErrorInterceptor {
  type HTTPErrorInterceptor (line 31) | type HTTPErrorInterceptor struct
    method WriteHeader (line 37) | func (i *HTTPErrorInterceptor) WriteHeader(code int) {
  function DefaultEngine (line 47) | func DefaultEngine() *BasicEngine {

FILE: router/mux/engine_test.go
  function TestEngine (line 13) | func TestEngine(t *testing.T) {

FILE: router/mux/render.go
  type Render (line 18) | type Render
  constant NEGOTIATE (line 21) | NEGOTIATE = "negotiate"
  function RegisterRender (line 34) | func RegisterRender(name string, r Render) {
  function getRender (line 40) | func getRender(cfg *config.EndpointConfig) Render {
  function getWithFallback (line 53) | func getWithFallback(key string, fallback Render) Render {
  function jsonRender (line 68) | func jsonRender(w http.ResponseWriter, response *proxy.Response) {
  function jsonCollectionRender (line 83) | func jsonCollectionRender(w http.ResponseWriter, response *proxy.Respons...
  function stringRender (line 103) | func stringRender(w http.ResponseWriter, response *proxy.Response) {
  function noopRender (line 122) | func noopRender(w http.ResponseWriter, response *proxy.Response) {

FILE: router/mux/render_test.go
  function TestRender_unknown (line 20) | func TestRender_unknown(t *testing.T) {
  function TestRender_string (line 81) | func TestRender_string(t *testing.T) {
  function TestRender_string_noData (line 142) | func TestRender_string_noData(t *testing.T) {
  function TestRegisterRender (line 204) | func TestRegisterRender(t *testing.T) {
  function TestRender_noop (line 229) | func TestRender_noop(t *testing.T) {
  function TestRender_noop_nilBody (line 290) | func TestRender_noop_nilBody(t *testing.T) {
  function TestRender_noop_nilResponse (line 337) | func TestRender_noop_nilResponse(t *testing.T) {

FILE: router/mux/router.go
  constant DefaultDebugPattern (line 22) | DefaultDebugPattern = "/__debug/"
  constant DefaultEchoPattern (line 23) | DefaultEchoPattern  = "/__echo/"
  constant logPrefix (line 24) | logPrefix           = "[SERVICE: Mux]"
  type RunServerFunc (line 28) | type RunServerFunc
  type Config (line 31) | type Config struct
  type HandlerMiddleware (line 43) | type HandlerMiddleware interface
  function DefaultFactory (line 48) | func DefaultFactory(pf proxy.Factory, logger logging.Logger) router.Fact...
  function NewFactory (line 64) | func NewFactory(cfg Config) router.Factory {
  type factory (line 71) | type factory struct
    method New (line 76) | func (rf factory) New() router.Router {
    method NewWithContext (line 81) | func (rf factory) NewWithContext(ctx context.Context) router.Router {
  type httpRouter (line 85) | type httpRouter struct
    method Run (line 98) | func (r httpRouter) Run(cfg config.ServiceConfig) {
    method registerKrakendEndpoints (line 146) | func (r httpRouter) registerKrakendEndpoints(endpoints []*config.Endpo...
    method registerKrakendEndpoint (line 158) | func (r httpRouter) registerKrakendEndpoint(method string, endpoint *c...
    method handler (line 182) | func (r httpRouter) handler() http.Handler {
  function HealthHandler (line 92) | func HealthHandler(w http.ResponseWriter, _ *http.Request) {

FILE: router/mux/router_test.go
  function TestDefaultFactory_ok (line 26) | func TestDefaultFactory_ok(t *testing.T) {
  function TestDefaultFactory_ko (line 138) | func TestDefaultFactory_ko(t *testing.T) {
  function TestDefaultFactory_proxyFactoryCrash (line 195) | func TestDefaultFactory_proxyFactoryCrash(t *testing.T) {
  function TestRunServer_ko (line 237) | func TestRunServer_ko(t *testing.T) {
  function checkResponseIs404 (line 271) | func checkResponseIs404(t *testing.T, req *http.Request) {
  type noopProxyFactory (line 305) | type noopProxyFactory
    method New (line 307) | func (n noopProxyFactory) New(_ *config.EndpointConfig) (proxy.Proxy, ...
  type erroredProxyFactory (line 316) | type erroredProxyFactory struct
    method New (line 320) | func (e erroredProxyFactory) New(_ *config.EndpointConfig) (proxy.Prox...
  type identityMiddleware (line 324) | type identityMiddleware struct
    method Handler (line 326) | func (identityMiddleware) Handler(h http.Handler) http.Handler {

FILE: router/negroni/router.go
  function DefaultFactory (line 22) | func DefaultFactory(pf proxy.Factory, logger logging.Logger, middlewares...
  function DefaultConfig (line 27) | func DefaultConfig(pf proxy.Factory, logger logging.Logger, middlewares ...
  function DefaultConfigWithRouter (line 33) | func DefaultConfigWithRouter(pf proxy.Factory, logger logging.Logger, mu...
  function NewGorillaRouter (line 40) | func NewGorillaRouter() *gorilla.Router {
  function newNegroniEngine (line 44) | func newNegroniEngine(muxEngine *gorilla.Router, middlewares ...negroni....
  type negroniEngine (line 55) | type negroniEngine struct
    method Handle (line 61) | func (e negroniEngine) Handle(pattern, method string, handler http.Han...
    method ServeHTTP (line 66) | func (e negroniEngine) ServeHTTP(w http.ResponseWriter, r *http.Reques...

FILE: router/negroni/router_test.go
  function TestDefaultFactory_ok (line 25) | func TestDefaultFactory_ok(t *testing.T) {
  function TestDefaultFactory_middlewares (line 126) | func TestDefaultFactory_middlewares(t *testing.T) {
  function TestDefaultFactory_ko (line 205) | func TestDefaultFactory_ko(t *testing.T) {
  function TestDefaultFactory_proxyFactoryCrash (line 263) | func TestDefaultFactory_proxyFactoryCrash(t *testing.T) {
  type dummyMiddleware (line 305) | type dummyMiddleware struct
    method ServeHTTP (line 309) | func (d dummyMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Req...
  function checkResponseIs404 (line 314) | func checkResponseIs404(t *testing.T, req *http.Request) {
  type noopProxyFactory (line 348) | type noopProxyFactory
    method New (line 350) | func (n noopProxyFactory) New(_ *config.EndpointConfig) (proxy.Proxy, ...
  type erroredProxyFactory (line 359) | type erroredProxyFactory struct
    method New (line 363) | func (e erroredProxyFactory) New(_ *config.EndpointConfig) (proxy.Prox...
  type identityMiddleware (line 367) | type identityMiddleware struct
    method Handler (line 369) | func (identityMiddleware) Handler(h http.Handler) http.Handler {

FILE: router/router.go
  type Router (line 15) | type Router interface
  type RouterFunc (line 21) | type RouterFunc
    method Run (line 24) | func (f RouterFunc) Run(cfg config.ServiceConfig) { f(cfg) }
  type Factory (line 27) | type Factory interface

FILE: sd/dnssrv/subscriber.go
  constant Namespace (line 20) | Namespace = "dns"
  constant DefaultTTL (line 21) | DefaultTTL = 30 * time.Second
  constant MinTTL (line 22) | MinTTL = time.Second
  function Register (line 25) | func Register() error {
  function SetTTL (line 32) | func SetTTL(d time.Duration) {
  function SubscriberFactory (line 45) | func SubscriberFactory(cfg *config.Backend) sd.Subscriber {
  function New (line 50) | func New(name string) sd.Subscriber {
  function NewDetailed (line 55) | func NewDetailed(name string, lookup lookup, ttl time.Duration) sd.Subsc...
  function NewDetailedWithScheme (line 61) | func NewDetailedWithScheme(name string, lookup lookup, ttl time.Duration...
  type lookup (line 86) | type lookup
  type subscriber (line 88) | type subscriber struct
    method Hosts (line 98) | func (s subscriber) Hosts() ([]string, error) {
    method update (line 112) | func (s subscriber) update() {
    method resolve (line 128) | func (s subscriber) resolve() ([]string, error) {
  function compact (line 170) | func compact(ws []uint16) []uint16 {
  function normalize (line 185) | func normalize(ws []uint16) []uint16 {
  function gcd (line 206) | func gcd(ws []uint16) uint16 {

FILE: sd/dnssrv/subscriber_test.go
  function ExampleRegister (line 16) | func ExampleRegister() {
  function ExampleNewDetailed (line 75) | func ExampleNewDetailed() {
  function ExampleNewDetailedWithScheme (line 130) | func ExampleNewDetailedWithScheme() {
  function TestSubscriber_LoockupError (line 185) | func TestSubscriber_LoockupError(t *testing.T) {
  function TestSubscriber_ResolveVeryLarge (line 201) | func TestSubscriber_ResolveVeryLarge(t *testing.T) {
  function Examplecompact_basicweights (line 221) | func Examplecompact_basicweights() {
  function Examplecompact_custom_weights (line 240) | func Examplecompact_custom_weights() {

FILE: sd/loadbalancing.go
  type Balancer (line 14) | type Balancer interface
  function NewBalancer (line 24) | func NewBalancer(subscriber Subscriber) Balancer {
  function NewRoundRobinLB (line 33) | func NewRoundRobinLB(subscriber Subscriber) Balancer {
  type roundRobinLB (line 49) | type roundRobinLB struct
    method Host (line 55) | func (r *roundRobinLB) Host() (string, error) {
  function NewRandomLB (line 65) | func NewRandomLB(subscriber Subscriber) Balancer {
  type randomLB (line 75) | type randomLB struct
    method Host (line 81) | func (r *randomLB) Host() (string, error) {
  type balancer (line 89) | type balancer struct
    method hosts (line 93) | func (b *balancer) hosts() ([]string, error) {
  type nopBalancer (line 104) | type nopBalancer
    method Host (line 106) | func (b nopBalancer) Host() (string, error) { return string(b), nil }

FILE: sd/loadbalancing_benchmark_test.go
  function BenchmarkLB (line 16) | func BenchmarkLB(b *testing.B) {
  function BenchmarkLB_parallel (line 36) | func BenchmarkLB_parallel(b *testing.B) {

FILE: sd/loadbalancing_test.go
  function ExampleNewRoundRobinLB (line 14) | func ExampleNewRoundRobinLB() {
  function TestRoundRobinLB (line 37) | func TestRoundRobinLB(t *testing.T) {
  function TestRoundRobinLB_noEndpoints (line 79) | func TestRoundRobinLB_noEndpoints(t *testing.T) {
  function ExampleNewRandomLB (line 88) | func ExampleNewRandomLB() {
  function TestRandomLB (line 120) | func TestRandomLB(t *testing.T) {
  function TestRandomLB_single (line 153) | func TestRandomLB_single(t *testing.T) {
  function TestRandomLB_noEndpoints (line 170) | func TestRandomLB_noEndpoints(t *testing.T) {
  type erroredSubscriber (line 179) | type erroredSubscriber
    method Hosts (line 181) | func (s erroredSubscriber) Hosts() ([]string, error) { return []string...
  function TestRoundRobinLB_erroredSubscriber (line 183) | func TestRoundRobinLB_erroredSubscriber(t *testing.T) {
  function TestRandomLB_erroredSubscriber (line 192) | func TestRandomLB_erroredSubscriber(t *testing.T) {

FILE: sd/register.go
  function GetRegister (line 10) | func GetRegister() *Register {
  type untypedRegister (line 14) | type untypedRegister interface
  type Register (line 21) | type Register struct
    method Register (line 31) | func (r *Register) Register(name string, sf SubscriberFactory) error {
    method Get (line 38) | func (r *Register) Get(name string) SubscriberFactory {
  function initRegister (line 25) | func initRegister() *Register {

FILE: sd/register_test.go
  function TestGetRegister_Register_ok (line 11) | func TestGetRegister_Register_ok(t *testing.T) {
  function TestGetRegister_Get_unknown (line 40) | func TestGetRegister_Get_unknown(t *testing.T) {
  function TestGetRegister_Get_errored (line 46) | func TestGetRegister_Get_errored(t *testing.T) {

FILE: sd/subscriber.go
  type Subscriber (line 15) | type Subscriber interface
  type SubscriberFunc (line 21) | type SubscriberFunc
    method Hosts (line 24) | func (f SubscriberFunc) Hosts() ([]string, error) { return f() }
  type FixedSubscriber (line 27) | type FixedSubscriber
    method Hosts (line 30) | func (s FixedSubscriber) Hosts() ([]string, error) { return s, nil }
  type SubscriberFactory (line 33) | type SubscriberFactory
  function FixedSubscriberFactory (line 36) | func FixedSubscriberFactory(cfg *config.Backend) Subscriber {
  function NewRandomFixedSubscriber (line 41) | func NewRandomFixedSubscriber(hosts []string) FixedSubscriber {

FILE: test/integration_test.go
  function init (line 40) | func init() {
  function TestKrakenD_ginRouter (line 63) | func TestKrakenD_ginRouter(t *testing.T) {
  function TestKrakenD_gorillaRouter (line 99) | func TestKrakenD_gorillaRouter(t *testing.T) {
  function TestKrakenD_negroniRouter (line 110) | func TestKrakenD_negroniRouter(t *testing.T) {
  function TestKrakenD_httptreemuxRouter (line 122) | func TestKrakenD_httptreemuxRouter(t *testing.T) {
  function TestKrakenD_chiRouter (line 131) | func TestKrakenD_chiRouter(t *testing.T) {
  function testKrakenD (line 142) | func testKrakenD(t *testing.T, runRouter func(logging.Logger, *config.Se...
  function setupBackend (line 460) | func setupBackend(t *testing.T) (*config.ServiceConfig, error) {
  function loadConfig (line 591) | func loadConfig(data map[string]interface{}) (*config.ServiceConfig, err...

FILE: transport/http/client/executor.go
  type HTTPRequestExecutor (line 14) | type HTTPRequestExecutor
  function DefaultHTTPRequestExecutor (line 17) | func DefaultHTTPRequestExecutor(clientFactory HTTPClientFactory) HTTPReq...
  type HTTPClientFactory (line 24) | type HTTPClientFactory
  function NewHTTPClient (line 27) | func NewHTTPClient(_ context.Context) *http.Client { return defaultHTTPC...

FILE: transport/http/client/executor_test.go
  function TestDefaultHTTPRequestExecutor (line 15) | func TestDefaultHTTPRequestExecutor(t *testing.T) {

FILE: transport/http/client/graphql/graphql.go
  constant Namespace (line 23) | Namespace = "github.com/devopsfaith/krakend/transport/http/client/graphql"
  type OperationType (line 26) | type OperationType
  type OperationMethod (line 29) | type OperationMethod
  constant OperationMutation (line 33) | OperationMutation OperationType = "mutation"
  constant OperationQuery (line 35) | OperationQuery OperationType = "query"
  constant MethodPost (line 37) | MethodPost OperationMethod = http.MethodPost
  constant MethodGet (line 38) | MethodGet  OperationMethod = http.MethodGet
  type GraphQLRequest (line 42) | type GraphQLRequest struct
  type Options (line 49) | type Options struct
  function GetOptions (line 59) | func GetOptions(cfg config.ExtraConfig) (*Options, error) {
  function New (line 94) | func New(opt Options) *Extractor {
  type Extractor (line 152) | type Extractor struct
    method QueryFromBody (line 160) | func (e *Extractor) QueryFromBody(r io.Reader) (url.Values, error) {
    method BodyFromBody (line 181) | func (e *Extractor) BodyFromBody(r io.Reader) ([]byte, error) {
    method fromBody (line 189) | func (e *Extractor) fromBody(r io.Reader) (*GraphQLRequest, error) {
    method QueryFromParams (line 216) | func (e *Extractor) QueryFromParams(params map[string]string) (url.Val...
    method BodyFromParams (line 237) | func (e *Extractor) BodyFromParams(params map[string]string) ([]byte, ...

FILE: transport/http/client/graphql/graphql_test.go
  function ExampleExtractor (line 13) | func ExampleExtractor() {
  function ExampleExtractor_fromFile (line 96) | func ExampleExtractor_fromFile() {
  function ExampleExtractor_noReplacement (line 182) | func ExampleExtractor_noReplacement() {

FILE: transport/http/client/plugin/executor.go
  constant Namespace (line 16) | Namespace = "github.com/devopsfaith/krakend/transport/http/client/executor"
  function HTTPRequestExecutor (line 18) | func HTTPRequestExecutor(
  function HTTPRequestExecutorWithContext (line 25) | func HTTPRequestExecutorWithContext(

FILE: transport/http/client/plugin/plugin.go
  function RegisterClient (line 19) | func RegisterClient(
  type Registerer (line 26) | type Registerer interface
  type LoggerRegisterer (line 33) | type LoggerRegisterer interface
  type RegisterClientFunc (line 37) | type RegisterClientFunc
  function Load (line 42) | func Load(path, pattern string, rcf RegisterClientFunc) (int, error) {
  function LoadWithLogger (line 46) | func LoadWithLogger(path, pattern string, rcf RegisterClientFunc, logger...
  function load (line 54) | func load(plugins []string, rcf RegisterClientFunc, logger logging.Logge...
  function open (line 72) | func open(pluginName string, rcf RegisterClientFunc, logger logging.Logg...
  type Plugin (line 113) | type Plugin interface
  function defaultPluginOpener (line 120) | func defaultPluginOpener(name string) (Plugin, error) {
  type loaderError (line 124) | type loaderError struct
    method Error (line 129) | func (l loaderError) Error() string {
    method Len (line 137) | func (l loaderError) Len() int {
    method Errs (line 141) | func (l loaderError) Errs() []error {

FILE: transport/http/client/plugin/plugin_test.go
  function TestLoadWithLogger (line 21) | func TestLoadWithLogger(t *testing.T) {

FILE: transport/http/client/plugin/tests/main.go
  type registerer (line 16) | type registerer
    method RegisterLogger (line 20) | func (registerer) RegisterLogger(v interface{}) {
    method RegisterClients (line 29) | func (r registerer) RegisterClients(f func(
    method registerClients (line 36) | func (r registerer) registerClients(_ context.Context, extra map[strin...
  function main (line 60) | func main() {}
  type Logger (line 62) | type Logger interface

FILE: transport/http/client/status.go
  constant Namespace (line 17) | Namespace = "github.com/devopsfaith/krakend/http"
  type ErrInvalidStatus (line 23) | type ErrInvalidStatus struct
    method Error (line 29) | func (e *ErrInvalidStatus) Error() string {
  function NewErrInvalidStatusCode (line 33) | func NewErrInvalidStatusCode(resp *http.Response, errPrefix string) *Err...
  type HTTPStatusHandler (line 47) | type HTTPStatusHandler
  function GetHTTPStatusHandler (line 52) | func GetHTTPStatusHandler(remote *config.Backend) HTTPStatusHandler {
  function DefaultHTTPStatusHandler (line 69) | func DefaultHTTPStatusHandler(_ context.Context, resp *http.Response) (*...
  function DefaultHTTPStatusHandlerWithErrPrefix (line 79) | func DefaultHTTPStatusHandlerWithErrPrefix(errPrefix string) HTTPStatusH...
  function ErrorHTTPStatusHandler (line 89) | func ErrorHTTPStatusHandler(ctx context.Context, resp *http.Response) (*...
  function ErrorHTTPStatusHandlerWithErrPrefix (line 97) | func ErrorHTTPStatusHandlerWithErrPrefix(errPrefix string) HTTPStatusHan...
  function NoOpHTTPStatusHandler (line 108) | func NoOpHTTPStatusHandler(_ context.Context, resp *http.Response) (*htt...
  function DetailedHTTPStatusHandler (line 113) | func DetailedHTTPStatusHandler(name string) HTTPStatusHandler {
  function DetailedHTTPStatusHandlerWithErrPrefix (line 129) | func DetailedHTTPStatusHandlerWithErrPrefix(name, errPrefix string) HTTP...
  function newHTTPResponseError (line 143) | func newHTTPResponseError(resp *http.Response) HTTPResponseError {
  type HTTPResponseError (line 159) | type HTTPResponseError struct
    method Error (line 166) | func (r HTTPResponseError) Error() string {
    method StatusCode (line 171) | func (r HTTPResponseError) StatusCode() int {
    method Encoding (line 176) | func (r HTTPResponseError) Encoding() string {
  type NamedHTTPResponseError (line 181) | type NamedHTTPResponseError struct
    method Name (line 187) | func (r NamedHTTPResponseError) Name() string {

FILE: transport/http/client/status_test.go
  function TestDetailedHTTPStatusHandler (line 17) | func TestDetailedHTTPStatusHandler(t *testing.T) {
  function TestDefaultHTTPStatusHandler (line 91) | func TestDefaultHTTPStatusHandler(t *testing.T) {

FILE: transport/http/server/plugin/plugin.go
  function RegisterHandler (line 19) | func RegisterHandler(
  type Registerer (line 26) | type Registerer interface
  type LoggerRegisterer (line 33) | type LoggerRegisterer interface
  type RegisterHandlerFunc (line 37) | type RegisterHandlerFunc
  function Load (line 42) | func Load(path, pattern string, rcf RegisterHandlerFunc) (int, error) {
  function LoadWithLogger (line 46) | func LoadWithLogger(path, pattern string, rcf RegisterHandlerFunc, logge...
  function load (line 54) | func load(plugins []string, rcf RegisterHandlerFunc, logger logging.Logg...
  function open (line 72) | func open(pluginName string, rcf RegisterHandlerFunc, logger logging.Log...
  type Plugin (line 113) | type Plugin interface
  function defaultPluginOpener (line 120) | func defaultPluginOpener(name string) (Plugin, error) {
  type loaderError (line 124) | type loaderError struct
    method Error (line 129) | func (l loaderError) Error() string {
    method Len (line 137) | func (l loaderError) Len() int {
    method Errs (line 141) | func (l loaderError) Errs() []error {

FILE: transport/http/server/plugin/plugin_test.go
  function TestLoadWithLogger (line 21) | func TestLoadWithLogger(t *testing.T) {

FILE: transport/http/server/plugin/server.go
  constant Namespace (line 13) | Namespace = "github_com/devopsfaith/krakend/transport/http/server/handler"
  constant logPrefix (line 14) | logPrefix = "[PLUGIN: Server]"
  type RunServer (line 16) | type RunServer
  function New (line 18) | func New(logger logging.Logger, next RunServer) RunServer {

FILE: transport/http/server/plugin/tests/main.go
  type registerer (line 15) | type registerer
    method RegisterLogger (line 19) | func (registerer) RegisterLogger(v interface{}) {
    method RegisterHandlers (line 28) | func (r registerer) RegisterHandlers(f func(
    method registerHandlers (line 35) | func (registerer) registerHandlers(_ context.Context, _ map[string]int...
  function main (line 64) | func main() {}
  type Logger (line 66) | type Logger interface

FILE: transport/http/server/server.go
  type ToHTTPError (line 28) | type ToHTTPError
  function DefaultToHTTPError (line 32) | func DefaultToHTTPError(_ error) int {
  constant HeaderCompleteResponseValue (line 39) | HeaderCompleteResponseValue = "true"
  constant HeaderIncompleteResponseValue (line 42) | HeaderIncompleteResponseValue = "false"
  function InitHTTPDefaultTransport (line 63) | func InitHTTPDefaultTransport(cfg config.ServiceConfig) {
  function InitHTTPDefaultTransportWithLogger (line 67) | func InitHTTPDefaultTransportWithLogger(cfg config.ServiceConfig, logger...
  function NewTransport (line 82) | func NewTransport(cfg config.ServiceConfig, logger logging.Logger) *http...
  function RunServer (line 105) | func RunServer(ctx context.Context, cfg config.ServiceConfig, handler ht...
  function RunServerWithLoggerFactory (line 109) | func RunServerWithLoggerFactory(l logging.Logger) func(context.Context, ...
  function NewServer (line 164) | func NewServer(cfg config.ServiceConfig, handler http.Handler) *http.Ser...
  function NewServerWithLogger (line 168) | func NewServerWithLogger(cfg config.ServiceConfig, handler http.Handler,...
  function ParseTLSConfig (line 186) | func ParseTLSConfig(cfg *config.TLS) *tls.Config {
  function ParseTLSConfigWithLogger (line 190) | func ParseTLSConfigWithLogger(cfg *config.TLS, logger logging.Logger) *t...
  function ParseClientTLSConfigWithLogger (line 229) | func ParseClientTLSConfigWithLogger(cfg *config.ClientTLS, logger loggin...
  function loadCertPool (line 244) | func loadCertPool(disableSystemCaPool bool, caCerts []string, logger log...
  function loadClientCerts (line 264) | func loadClientCerts(certFiles []config.ClientTLSCert, logger logging.Lo...
  function parseTLSVersion (line 279) | func parseTLSVersion(key string) uint16 {
  function parseCurveIDs (line 286) | func parseCurveIDs(curvePreferences []uint16) []tls.CurveID {
  function parseCipherSuites (line 299) | func parseCipherSuites(cipherSuites []uint16) []uint16 {

FILE: transport/http/server/server_test.go
  function init (line 26) | func init() {
  function TestRunServer_TLS (line 30) | func TestRunServer_TLS(t *testing.T) {
  function TestRunServer_MTLS (line 98) | func TestRunServer_MTLS(t *testing.T) {
  function TestRunServer_MTLSOldConfigFormat (line 181) | func TestRunServer_MTLSOldConfigFormat(t *testing.T) {
  function TestRunServer_plain (line 260) | func TestRunServer_plain(t *testing.T) {
  function TestRunServer_h2c (line 293) | func TestRunServer_h2c(t *testing.T) {
  function TestRunServer_disabledTLS (line 330) | func TestRunServer_disabledTLS(t *testing.T) {
  function TestRunServer_err (line 368) | func TestRunServer_err(t *testing.T) {
  function TestRunServer_errBadKeys (line 400) | func TestRunServer_errBadKeys(t *testing.T) {
  function Test_parseTLSVersion (line 419) | func Test_parseTLSVersion(t *testing.T) {
  function Test_parseCurveIDs (line 437) | func Test_parseCurveIDs(t *testing.T) {
  function Test_parseCipherSuites (line 447) | func Test_parseCipherSuites(t *testing.T) {
  function dummyHandler (line 457) | func dummyHandler(rw http.ResponseWriter, req *http.Request) {
  function testKeysAreAvailable (line 461) | func testKeysAreAvailable(t *testing.T) {
  function httpsClient (line 481) | func httpsClient(cert string) (*http.Client, error) {
  function mtlsClient (line 508) | func mtlsClient(certPath, keyPath string) (*http.Client, error) {
  function h2cClient (line 542) | func h2cClient() *http.Client {
  function newPort (line 554) | func newPort() int {
  function TestRunServer_MultipleTLS (line 558) | func TestRunServer_MultipleTLS(t *testing.T) {
  function overrideHostTransport (line 640) | func overrideHostTransport(client *http.Client) {

FILE: transport/http/server/tls_test.go
  type certDef (line 19) | type certDef struct
    method Org (line 25) | func (c certDef) Org() string {
  function init (line 32) | func init() {
  function generateNamedCert (line 53) | func generateNamedCert(hostCert certDef) error {
Condensed preview — 158 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (782K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 496,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/report-vulnerability.md",
    "chars": 421,
    "preview": "---\nname: Report vulnerability\nabout: Report a vulnerability\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\nFor **private vuln"
  },
  {
    "path": ".github/label-commenter-config.yml",
    "chars": 5164,
    "preview": "comment:\n  footer:  |\n    ---\n    > This is an automated comment. Responding to the bot or mentioning it won't have any "
  },
  {
    "path": ".github/workflows/go.yml",
    "chars": 446,
    "preview": "name: Go\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n\n  build:\n    runs-on: ub"
  },
  {
    "path": ".github/workflows/labels.yml",
    "chars": 211,
    "preview": "name: Label commenter\non:\n  issues:\n    types: [labeled, unlabeled]\n  pull_request_target:\n    types: [labeled, unlabele"
  },
  {
    "path": ".github/workflows/lock-threads.yml",
    "chars": 898,
    "preview": "name: 'Lock Threads'\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-"
  },
  {
    "path": ".gitignore",
    "chars": 108,
    "preview": "vendor\nserver.rsa.crt\nserver.rsa.key\n*.pem\n*.json\n*.toml\n*.dot\n*.out\n*.so\nbench_res\n.cover\n.idea\n\n*DS_Store\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3213,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2072,
    "preview": "# Contributing\n\nThank you for your interest in contributing to Lura, there are several ways\nyou can contribute and make "
  },
  {
    "path": "LICENSE",
    "chars": 583,
    "preview": "Copyright © 2021 Lura Project a Series of LF Projects, LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "Makefile",
    "chars": 986,
    "preview": ".PHONY: all test build benchmark\n\nOS := $(shell uname | tr '[:upper:]' '[:lower:]')\nGIT_COMMIT := $(shell git rev-parse "
  },
  {
    "path": "README.md",
    "chars": 6078,
    "preview": "<img src=\"https://luraproject.org/images/lura-logo-header.svg\" width=\"300\" />\n\n# The Lura Project framework\n\n[![Go Repor"
  },
  {
    "path": "SECURITY.md",
    "chars": 716,
    "preview": "# Security Policy\nLura only fixes the latest version of the software, and does not patch prior versions.\n\n## Reporting a"
  },
  {
    "path": "async/asyncagent.go",
    "chars": 2846,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n */\npackage async\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com"
  },
  {
    "path": "async/asyncagent_test.go",
    "chars": 1918,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage async\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/luraproject/lura/v2/"
  },
  {
    "path": "backoff/backoff.go",
    "chars": 2082,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage backoff contains some basic implementations and a selector by strateg"
  },
  {
    "path": "backoff/backoff_test.go",
    "chars": 644,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage backoff\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestExponentialBackoff(t *t"
  },
  {
    "path": "config/config.go",
    "chars": 28045,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage config defines the config structs and some config parser interfaces a"
  },
  {
    "path": "config/config_test.go",
    "chars": 9023,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc "
  },
  {
    "path": "config/parser.go",
    "chars": 15392,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n)\n\n// Parser read"
  },
  {
    "path": "config/parser_test.go",
    "chars": 8706,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestNewParser_ok(t *testing.T)"
  },
  {
    "path": "config/uri.go",
    "chars": 2923,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar (\n\tendpointURLKeysPa"
  },
  {
    "path": "config/uri_test.go",
    "chars": 2579,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage config\n\nimport \"testing\"\n\nfunc TestURIParser_cleanHosts(t *testing.T) {\n"
  },
  {
    "path": "core/version.go",
    "chars": 852,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage core contains some basic constants and variables\n*/\npackage core\n\nimp"
  },
  {
    "path": "docs/BENCHMARKS.md",
    "chars": 19516,
    "preview": "Benchmarks\n---\n\nHere you'll find some benchmarks of the different components of the Lura framework in several scenarios."
  },
  {
    "path": "docs/CONFIG.md",
    "chars": 2138,
    "preview": "# Configuration file\n\nThe configuration file needs to be a `json` file. The viper parser supports other formats but they"
  },
  {
    "path": "docs/OVERVIEW.md",
    "chars": 4816,
    "preview": "# Overview\n\n## The Lura rules\n\n* [Reactive is key](http://www.reactivemanifesto.org/)\n* Reactive is key (yes, it is very"
  },
  {
    "path": "docs/README.md",
    "chars": 592,
    "preview": "<img src=\"https://luraproject.org/images/lura-logo-header.svg\" width=\"300\" />\n\n# The Lura Project\n\n## How to use it\n\nVis"
  },
  {
    "path": "encoding/encoding.go",
    "chars": 2958,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage encoding provides basic decoding implementations.\n\nDecode decodes HTT"
  },
  {
    "path": "encoding/encoding_test.go",
    "chars": 4113,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage encoding\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/l"
  },
  {
    "path": "encoding/json_benchmark_test.go",
    "chars": 979,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage encoding\n\nimport (\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc BenchmarkDecoder("
  },
  {
    "path": "encoding/json_test.go",
    "chars": 4574,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage encoding\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc"
  },
  {
    "path": "encoding/register.go",
    "chars": 1588,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage encoding\n\nimport (\n\t\"io\"\n\n\t\"github.com/luraproject/lura/v2/register\"\n)\n\n"
  },
  {
    "path": "go.mod",
    "chars": 1783,
    "preview": "module github.com/luraproject/lura/v2\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/dimfeld/httptreemux/v5 v5.5.0\n\tgithub.com/gin-co"
  },
  {
    "path": "go.sum",
    "chars": 9049,
    "preview": "github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\ngithub.com/bytedance/gopkg v0.1.3/go.m"
  },
  {
    "path": "logging/log.go",
    "chars": 2704,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage logging provides a simple logger interface and implementations\n*/\npa"
  },
  {
    "path": "logging/log_test.go",
    "chars": 2023,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage logging\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"testing\"\n)\n\ncons"
  },
  {
    "path": "plugin/plugin.go",
    "chars": 626,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage plugin provides tools for loading and registering plugins\n*/\npackage "
  },
  {
    "path": "plugin/plugin_test.go",
    "chars": 1806,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestScan_ok(t *testing.T) {\n\tt"
  },
  {
    "path": "proxy/balancing.go",
    "chars": 4704,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\n\t\"github.com/luraproject/lura/v2/"
  },
  {
    "path": "proxy/balancing_benchmark_test.go",
    "chars": 2285,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/luraproje"
  },
  {
    "path": "proxy/balancing_test.go",
    "chars": 3793,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"git"
  },
  {
    "path": "proxy/concurrent.go",
    "chars": 2732,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/lurapr"
  },
  {
    "path": "proxy/concurrent_benchmark_test.go",
    "chars": 494,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/luraproject/"
  },
  {
    "path": "proxy/concurrent_test.go",
    "chars": 3724,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.c"
  },
  {
    "path": "proxy/factory.go",
    "chars": 3844,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"github.com/luraproject/lura/v2/config\"\n\t\"github.com/lu"
  },
  {
    "path": "proxy/factory_test.go",
    "chars": 4301,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimpo"
  },
  {
    "path": "proxy/formatter.go",
    "chars": 8164,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/krakend/flatm"
  },
  {
    "path": "proxy/formatter_benchmark_test.go",
    "chars": 8119,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/lura"
  },
  {
    "path": "proxy/formatter_test.go",
    "chars": 17051,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/luraproje"
  },
  {
    "path": "proxy/graphql.go",
    "chars": 3667,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"strconv\"\n\t"
  },
  {
    "path": "proxy/graphql_test.go",
    "chars": 3344,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"reflect\"\n\t\"strings\"\n"
  },
  {
    "path": "proxy/headers_filter.go",
    "chars": 2118,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\n\t\"github.com/luraproject/lura/v2/config\"\n\t\"g"
  },
  {
    "path": "proxy/headers_filter_test.go",
    "chars": 3217,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/luraproject/lura/v2/"
  },
  {
    "path": "proxy/http.go",
    "chars": 5629,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n"
  },
  {
    "path": "proxy/http_benchmark_test.go",
    "chars": 447,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/luraproject/lura/v2/"
  },
  {
    "path": "proxy/http_response.go",
    "chars": 2234,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"compress/gzip\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github."
  },
  {
    "path": "proxy/http_response_test.go",
    "chars": 4337,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"compress/gzip\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http"
  },
  {
    "path": "proxy/http_test.go",
    "chars": 15844,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\""
  },
  {
    "path": "proxy/logging.go",
    "chars": 1105,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/luraproject/"
  },
  {
    "path": "proxy/logging_test.go",
    "chars": 4046,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gith"
  },
  {
    "path": "proxy/merging.go",
    "chars": 14224,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\""
  },
  {
    "path": "proxy/merging_benchmark_test.go",
    "chars": 4650,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lurap"
  },
  {
    "path": "proxy/merging_test.go",
    "chars": 26542,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\""
  },
  {
    "path": "proxy/plugin/modifier.go",
    "chars": 5819,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage plugin provides tools for loading and registering proxy plugins\n*/\npa"
  },
  {
    "path": "proxy/plugin/modifier_test.go",
    "chars": 5592,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimp"
  },
  {
    "path": "proxy/plugin/tests/error/main.go",
    "chars": 2020,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc main() {}\n\nvar Modif"
  },
  {
    "path": "proxy/plugin/tests/logger/main.go",
    "chars": 5611,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"path\"\n)\n\nf"
  },
  {
    "path": "proxy/plugin.go",
    "chars": 7811,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\n\t\"github.com/lurapro"
  },
  {
    "path": "proxy/plugin_test.go",
    "chars": 5326,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimpo"
  },
  {
    "path": "proxy/proxy.go",
    "chars": 3270,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage proxy provides proxy and proxy middleware interfaces and implementati"
  },
  {
    "path": "proxy/proxy_test.go",
    "chars": 2633,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"te"
  },
  {
    "path": "proxy/query_strings_filter.go",
    "chars": 2203,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\n\t\"github.com/luraproject/lura/v2/"
  },
  {
    "path": "proxy/query_strings_filter_test.go",
    "chars": 3557,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/luraproject/lura/v2/"
  },
  {
    "path": "proxy/register.go",
    "chars": 894,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"github.com/luraproject/lura/v2/register\"\n)\n\nfunc NewRe"
  },
  {
    "path": "proxy/register_test.go",
    "chars": 2163,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestNewRegister_responseCo"
  },
  {
    "path": "proxy/request.go",
    "chars": 2398,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/url\"\n)\n\n// Request contains the dat"
  },
  {
    "path": "proxy/request_benchmark_test.go",
    "chars": 544,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport \"testing\"\n\nfunc BenchmarkRequestGeneratePath(b *testing.B)"
  },
  {
    "path": "proxy/request_test.go",
    "chars": 3522,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRequest"
  },
  {
    "path": "proxy/shadow.go",
    "chars": 4615,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/luraproject/lura/v2/con"
  },
  {
    "path": "proxy/shadow_test.go",
    "chars": 4716,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"sync/atomic\"\n\t\"testing\"\n"
  },
  {
    "path": "proxy/stack_benchmark_test.go",
    "chars": 10097,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/lurap"
  },
  {
    "path": "proxy/stack_test.go",
    "chars": 2006,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimpo"
  },
  {
    "path": "proxy/static.go",
    "chars": 3127,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/lurapro"
  },
  {
    "path": "proxy/static_test.go",
    "chars": 8992,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage proxy\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com"
  },
  {
    "path": "register/register.go",
    "chars": 2090,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage register offers tools for creating and managing registers.\n*/\npackag"
  },
  {
    "path": "register/register_test.go",
    "chars": 925,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage register\n\nimport \"testing\"\n\nfunc TestNamespaced(t *testing.T) {\n\tr := Ne"
  },
  {
    "path": "router/chi/endpoint.go",
    "chars": 1131,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage chi\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/lura"
  },
  {
    "path": "router/chi/endpoint_benchmark_test.go",
    "chars": 3253,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage chi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/ht"
  },
  {
    "path": "router/chi/endpoint_test.go",
    "chars": 8898,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage chi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net"
  },
  {
    "path": "router/chi/router.go",
    "chars": 4710,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage chi provides some basic implementations for building routers based o"
  },
  {
    "path": "router/chi/router_test.go",
    "chars": 8260,
    "preview": "//go:build !race\n// +build !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage chi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"er"
  },
  {
    "path": "router/gin/debug.go",
    "chars": 860,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"io\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/luraproje"
  },
  {
    "path": "router/gin/debug_test.go",
    "chars": 1540,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\""
  },
  {
    "path": "router/gin/echo.go",
    "chars": 1221,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype ech"
  },
  {
    "path": "router/gin/echo_test.go",
    "chars": 2822,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testin"
  },
  {
    "path": "router/gin/endpoint.go",
    "chars": 5419,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/textproto\"\n\n\t\"github.com/gin-gonic"
  },
  {
    "path": "router/gin/endpoint_benchmark_test.go",
    "chars": 3337,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/ht"
  },
  {
    "path": "router/gin/endpoint_test.go",
    "chars": 12832,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net"
  },
  {
    "path": "router/gin/engine.go",
    "chars": 8144,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/"
  },
  {
    "path": "router/gin/engine_test.go",
    "chars": 2009,
    "preview": "package gin\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github"
  },
  {
    "path": "router/gin/render.go",
    "chars": 3618,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"g"
  },
  {
    "path": "router/gin/render_test.go",
    "chars": 16493,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/ht"
  },
  {
    "path": "router/gin/router.go",
    "chars": 5526,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage gin provides some basic implementations for building routers based on"
  },
  {
    "path": "router/gin/router_test.go",
    "chars": 8932,
    "preview": "//go:build !race\n// +build !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage gin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"er"
  },
  {
    "path": "router/gin/safecast.go",
    "chars": 1416,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage gin provides some basic implementations for building routers based on"
  },
  {
    "path": "router/gorilla/router.go",
    "chars": 1951,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage gorilla provides some basic implementations for building routers base"
  },
  {
    "path": "router/gorilla/router_test.go",
    "chars": 7099,
    "preview": "//go:build !race\n// +build !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage gorilla\n\nimport (\n\t\"bytes\"\n\t\"context\"\n"
  },
  {
    "path": "router/helper.go",
    "chars": 569,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage router\n\nimport (\n\t\"github.com/luraproject/lura/v2/config\"\n)\n\nfunc IsVali"
  },
  {
    "path": "router/helper_test.go",
    "chars": 2762,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage router\n\n// func TestIsValidSequentialEndpoint_ok(t *testing.T) {\n\n// \ten"
  },
  {
    "path": "router/httptreemux/router.go",
    "chars": 2013,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage httptreemux provides some basic implementations for building routers"
  },
  {
    "path": "router/httptreemux/router_test.go",
    "chars": 7089,
    "preview": "//go:build !race\n// +build !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage httptreemux\n\nimport (\n\t\"bytes\"\n\t\"conte"
  },
  {
    "path": "router/mux/debug.go",
    "chars": 922,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/luraproje"
  },
  {
    "path": "router/mux/debug_test.go",
    "chars": 1442,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\""
  },
  {
    "path": "router/mux/echo.go",
    "chars": 1277,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype echoResponse st"
  },
  {
    "path": "router/mux/echo_test.go",
    "chars": 2700,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testin"
  },
  {
    "path": "router/mux/endpoint.go",
    "chars": 6002,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"st"
  },
  {
    "path": "router/mux/endpoint_benchmark_test.go",
    "chars": 3225,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/ht"
  },
  {
    "path": "router/mux/endpoint_test.go",
    "chars": 9203,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io"
  },
  {
    "path": "router/mux/engine.go",
    "chars": 2439,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/luraproject/lura/v2/tran"
  },
  {
    "path": "router/mux/engine_test.go",
    "chars": 952,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\""
  },
  {
    "path": "router/mux/render.go",
    "chars": 2913,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/l"
  },
  {
    "path": "router/mux/render_test.go",
    "chars": 10826,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\""
  },
  {
    "path": "router/mux/router.go",
    "chars": 5237,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage mux provides some basic implementations for building routers based on"
  },
  {
    "path": "router/mux/router_test.go",
    "chars": 8242,
    "preview": "//go:build !race\n// +build !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage mux\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"er"
  },
  {
    "path": "router/negroni/router.go",
    "chars": 2332,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage negroni provides some basic implementations for building routers bas"
  },
  {
    "path": "router/negroni/router_test.go",
    "chars": 9503,
    "preview": "//go:build !race\n// +build !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage negroni\n\nimport (\n\t\"bytes\"\n\t\"context\"\n"
  },
  {
    "path": "router/router.go",
    "chars": 751,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage router defines some interfaces and common helpers for router adapter"
  },
  {
    "path": "sd/dnssrv/subscriber.go",
    "chars": 4568,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage dnssrv defines some implementations for a dns based service discovery"
  },
  {
    "path": "sd/dnssrv/subscriber_test.go",
    "chars": 4861,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage dnssrv\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "sd/loadbalancing.go",
    "chars": 2570,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage sd\n\nimport (\n\t\"errors\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\n\t\"github.com/valyala/f"
  },
  {
    "path": "sd/loadbalancing_benchmark_test.go",
    "chars": 1354,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage sd\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nvar balancerTestsCases = [][]string{\n\t"
  },
  {
    "path": "sd/loadbalancing_test.go",
    "chars": 4240,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage sd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/luraproje"
  },
  {
    "path": "sd/register.go",
    "chars": 1197,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage sd\n\nimport (\n\t\"github.com/luraproject/lura/v2/register\"\n)\n\n// GetRegiste"
  },
  {
    "path": "sd/register_test.go",
    "chars": 1616,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage sd\n\nimport (\n\t\"testing\"\n\n\t\"github.com/luraproject/lura/v2/config\"\n)\n\nfun"
  },
  {
    "path": "sd/subscriber.go",
    "chars": 1529,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage sd defines some interfaces and implementations for service discovery"
  },
  {
    "path": "test/doc.go",
    "chars": 132,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n   Package test contains the integration tests for the KrakenD framework\n*/\np"
  },
  {
    "path": "test/integration_test.go",
    "chars": 19539,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage test\n\nimpor"
  },
  {
    "path": "transport/http/client/executor.go",
    "chars": 1005,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\n\tPackage client provides some http helpers to create http clients and executo"
  },
  {
    "path": "transport/http/client/executor_test.go",
    "chars": 679,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http"
  },
  {
    "path": "transport/http/client/graphql/graphql.go",
    "chars": 6136,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage graphql offers a param extractor and basic types for building GraphQL"
  },
  {
    "path": "transport/http/client/graphql/graphql_test.go",
    "chars": 6139,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage graphql\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/luraproject/lura"
  },
  {
    "path": "transport/http/client/plugin/doc.go",
    "chars": 1461,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n//Package plugin provides plugin register interfaces for building http client pl"
  },
  {
    "path": "transport/http/client/plugin/executor.go",
    "chars": 2216,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"g"
  },
  {
    "path": "transport/http/client/plugin/plugin.go",
    "chars": 3114,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"plugin\"\n\t\"strings\"\n\n\t\"g"
  },
  {
    "path": "transport/http/client/plugin/plugin_test.go",
    "chars": 1398,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimp"
  },
  {
    "path": "transport/http/client/plugin/tests/main.go",
    "chars": 1868,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html\"\n\t\"net/http\"\n)\n\n// Cli"
  },
  {
    "path": "transport/http/client/status.go",
    "chars": 5970,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n"
  },
  {
    "path": "transport/http/client/status_test.go",
    "chars": 4015,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\""
  },
  {
    "path": "transport/http/server/plugin/doc.go",
    "chars": 1495,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n//Package plugin provides plugin register interfaces for building http handler p"
  },
  {
    "path": "transport/http/server/plugin/plugin.go",
    "chars": 3163,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"plugin\"\n\t\"strings\"\n\n\t\"g"
  },
  {
    "path": "transport/http/server/plugin/plugin_test.go",
    "chars": 1614,
    "preview": "//go:build integration || !race\n// +build integration !race\n\n// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimp"
  },
  {
    "path": "transport/http/server/plugin/server.go",
    "chars": 1978,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage plugin\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/luraproject/lura/v"
  },
  {
    "path": "transport/http/server/plugin/tests/main.go",
    "chars": 1970,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"html\"\n\t\"net/http\"\n)\n\n// HandlerRegist"
  },
  {
    "path": "transport/http/server/server.go",
    "chars": 9971,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\n/*\nPackage server provides tools to create http servers and handlers wrapping th"
  },
  {
    "path": "transport/http/server/server_test.go",
    "chars": 14567,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt"
  },
  {
    "path": "transport/http/server/tls_test.go",
    "chars": 3969,
    "preview": "// SPDX-License-Identifier: Apache-2.0\n\npackage server\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x5"
  }
]

About this extraction

This page contains the full source code of the luraproject/lura GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 158 files (684.6 KB), approximately 195.2k tokens, and a symbol index with 1134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!