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
[](https://goreportcard.com/report/github.com/luraproject/lura/v2)
[](https://godoc.org/github.com/luraproject/lura/v2)

[](https://gophers.slack.com/messages/lura)
[](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:

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
[](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"},
} {
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
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[\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.