From 0a05369e390319ed6abc7b867c7138c3900f604e Mon Sep 17 00:00:00 2001 From: Guillaume Gnaegi <58469901+ggnaegi@users.noreply.github.com> Date: Sun, 15 Oct 2023 22:09:29 +0200 Subject: [PATCH 1/4] #1731 Read the Docs configuration file v2 (#1733) * fixing the documentation, using Release/20.0 as base branch * using latest conf.py, created with sphinx-quickstart, fixing the warnings during documentation generation * Update .readthedocs.yaml * switching to threemammals.org for copyright * adding requirements file, updating readthedocs.yaml, adding formats pdf / epub and config for requirements file * fixing code block in websockets.rst * ok, now it should be fine... * Update kubernetes.rst: Review and fix markup code * Update websockets.rst: Review and fix markup * Update conf.py: Update release, author and copyright --------- Co-authored-by: Raman Maksimchuk --- .readthedocs.yaml | 31 +++ Ocelot.sln | 1 + docs/Makefile | 245 ++--------------- docs/autobuild.bat | 1 - docs/building/releaseprocess.rst | 1 + docs/conf.py | 359 +------------------------ docs/favicon.ico | Bin 8313 -> 0 bytes docs/features/authentication.rst | 2 +- docs/features/claimstransformation.rst | 4 +- docs/features/kubernetes.rst | 79 +++--- docs/features/loadbalancer.rst | 2 +- docs/features/websockets.rst | 62 ++--- docs/make.bat | 268 +----------------- docs/make.sh | 281 ------------------- docs/requirements.txt | 3 + 15 files changed, 157 insertions(+), 1182 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 docs/autobuild.bat delete mode 100644 docs/favicon.ico delete mode 100755 docs/make.sh create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..e495fe279 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,31 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +formats: + - pdf + - epub + +# Optional but recommended, declare the Python requirements required to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/Ocelot.sln b/Ocelot.sln index afcec039d..c4abd99a9 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .dockerignore = .dockerignore .editorconfig = .editorconfig .gitignore = .gitignore + .readthedocs.yaml = .readthedocs.yaml build.cake = build.cake build.ps1 = build.ps1 codeanalysis.ruleset = codeanalysis.ruleset diff --git a/docs/Makefile b/docs/Makefile index 61e3150d1..d4bb2cbb9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,225 +1,20 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ocelot.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ocelot.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Ocelot" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ocelot" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: epub3 -epub3: - $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 - @echo - @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -.PHONY: dummy -dummy: - $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy - @echo - @echo "Build finished. Dummy builder generates no files." +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/autobuild.bat b/docs/autobuild.bat deleted file mode 100644 index 6bcafb67c..000000000 --- a/docs/autobuild.bat +++ /dev/null @@ -1 +0,0 @@ -sphinx-autobuild.exe . .\_build\html\ \ No newline at end of file diff --git a/docs/building/releaseprocess.rst b/docs/building/releaseprocess.rst index 40da81567..b3746dd1b 100644 --- a/docs/building/releaseprocess.rst +++ b/docs/building/releaseprocess.rst @@ -16,6 +16,7 @@ Ocelot uses the following process to accept work into the NuGet packages. - Have I covered all my changes with tests at unit and acceptance level? - Have I updated any documentation that my changes may have affected? - Does my feature make sense, have I checked all of Ocelot's other features to make sure it doesn't already exist? + In order for a PR to be merged the following must have occured. - All new code is covered by unit tests. - All new code has at least 1 acceptance test covering the happy path. diff --git a/docs/conf.py b/docs/conf.py index 716ee20b9..b950954c6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,359 +1,28 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. # -# Ocelot documentation build configuration file, created by -# sphinx-quickstart on Wed Jul 20 08:57:27 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -# -- General configuration ------------------------------------------------ +project = 'Ocelot' +copyright = ' 2023, ThreeMammals Ocelot team' +author = 'Tom Pallister, Ocelot Core team at ThreeMammals and Ocelot GitHub community' +release = '20.0.1' -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [] -# Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] - -# markdown support -#from recommonmark.parser import CommonMarkParser - -#source_parsers = { -# '.md': CommonMarkParser, -#} - -source_suffix = ['.rst'] - - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'Ocelot' -copyright = '2016, Tom Pallister' -author = 'Tom Pallister' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.0.0' -# The full version, including alpha/beta/rc tags. -release = '1.0.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' -highlight_language = 'csharp' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# - -# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org -import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' - -if not on_rtd: # only import and set the theme if we're building docs locally - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - -# otherwise, readthedocs.org uses their theme by default, so no need to specify it - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = 'Ocelot v1.0.0' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -html_favicon = 'favicon.ico' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". +html_theme = 'alabaster' html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Ocelotdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'Ocelot.tex', 'Ocelot Documentation', - 'Tom Pallister', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# It false, will not define \strong, \code, itleref, \crossref ... but only -# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added -# packages. -# -# latex_keep_old_macro_names = True - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'Ocelot', 'Ocelot Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'Ocelot', 'Ocelot Documentation', - author, 'Ocelot', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 5e606f749c664af11832e2ee0309726dfb664e51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8313 zcmeG>2Ut``w{!2^UVvp61O!=nQIOs&l%=hJARv~Q=r)$sWp~{I*c)O*6BQL}ENHBW zCK|EC7{wlq3ikSg21_hIMzJTnnY-+QCV7c@-~YYueczkiJ9FloGc)JRJu~Ob+_p10RbTl{s86zs3Z)46zgh*M`Hf% zJUHYD1uzFf_~@}P5*9$XYK96>U)_J>f{yG5z|YQ7Cd#!^g)pZ`Yt$Nwv^t?E zFG}d2C6TE#2oV6nLPDeCLL%bALWH3aaS@Smk+A>}tFR8~R^gTr@WHEy4j7Jx5)C8T zd1;+B!Gs-}VE&yo!7)o8SP0(H_YHG*ARU%S8?tP>)OHn!0E0%O)2IwOoz7%3SR4VD z!)9}w?fJF>Hy8JAZZ58_LhoQdp=S>-S6Bb&fF2>C5fKsYezARG!g>dXM}(nHFea19 zVRM|gT&FM(SC6pwPHp=Ej{!1327!41oQDxStnCnRhtg8fo5qmPWWsQQLZ#6eOcon5 z?63nkMi4lKpi(LDRbdk#9Z+~wdymjW8h?O#gN@`lVxL;oWz=A=8hYXd;6@|(o)kuTUR8m?tX6&@` z=@m0Ro>^Hnf5F1)MT?g#ty#17^L6VtY}~YM`!_pkckbG~r|#gP!$*!DJN`q%*>mSF zT)cGoO4H3-KQ-UJbNAlQkAHdcwDsBZ7cXBSy`Tk{)v{{#j$S;d7fzuN6gtui!%N{q z@F-M|P?~+>0J>x}-!p6qLy$CY_16cOUg5bd4pMzR%h5Zc$>%XrjnwS#DK_;VX=YWd zL$51=IbhF8^b$Ym(vrR^w$q12XD*-tMW1dv2Rg)ZIjZvY{n2Her!Kf6e^{8%3w#`$O zA&w-K5w$^NbDs)*e@U?-KV@(}LHAO}Q)Fc1QNLSO(2pocgGgitbf zo1@oi3-c64lP+62QU=i+*hmjM(1JqPC=aq5feGlyCM=zm8`ep=lhj5{yWAY9Nu@Tb zG-Q{M&Lp`-Swk|+bJh#>fd|2?V_xh#%}chMWY2vR)&a?_GTP#WY2K(1UU zlxt1W1Z^X#k||BE`5i7v{d>M7y*z)Q#+d4rt40=JO-X9G@Q?7h2DOpI=ai`v^!qLR ztP+LHsMRM+j1r^)DP)c^$6&D?1S4KpBC$cHqR`yUJHzHfKl4Y!=E>A%e2!k0Fa-5L zCOes4s~c3LfG-!?u}Y(~OrH%R(CTnYNv<|ul4jFBC@kV91GBwxNgVRLWhAGwwcd^F^$A&Lk~z#U zvK92oD6!htEi_n?x0S@EgPw(jkfMe;^rSFh?U>a|mRd1kBz`E&Ej*DV23dYPhV1hN ztOK;N+Ws&RcM_TWNGAFP#7epeGTIY z5DlO9p1L83j2+CaT~L(}!_2HXssenl`GK=M5N=dPxG5Qd8tyc55ClRYL;KPrnJrl- zoVC4$MwXoztoU{zQC(wFqg@lJN|S0$8oA+}buKfap^>=&v^cC52okK}B~us*)R|~0VsDK@C20_C4<9;N5hr&Vi>}mbO}e*GG_78xR9Sb~lswdX zHi?6Ma3vX=p{yl93nve7@ftHu*}Q|gDB!+r4SbXY5fV4Ufu4H+FuHq{I@Y# z2BpPmnR#duwJ~3!e2dFQrhx4fWk#_fJwGecvf(i-=(m`eMOyvXM72t3nJHVd^63^d z)SM2ZF^P%XfSDxnQ7{#p&1Yo<}FgYMp- z=)A+R)Nm{O9hM^{x6a?;+3*z?y~|FnNq;mD^wiLt<6B7ByxIgLj7-?PQV|Crp1};w z@hbC!H3D)2C9VJ`fWhefA>fcFkl~^%Sbqd;$QqDOhWr=|%gX_W!C){M92lJQ*eo{B z!Is0Zb#UVI9r%1F9*0crg}L%>j&a#+t_|1D#>P%yV`C#gnT^2Qg!gwkFvs<)z$yas zw8Zt-;aX+29m3+Fly67(1dMVm(LFr8y-kGv!y5YA0gQsdupg(=X$&gC2`A%DiidqD zHF1E1?>Tx(7)=0U<0LO{huj0w@Cg0XdJ)~HDzByKvCI(ZShH=KZ}R+nc}m?HV^o)h zfj1SuY%f1})^zJhs^5aO)4y4G=-f|FKi_fqd~<6-VM#^xy4oWbZa<4o8>B3qv1t9y zqZjWyhY@;vhz^e<@BkG_ir^7yPk|DQ_T*FHu|t3)H~>!_;oj0F{nRQ^o`cK~@z{rk z1fcsy)-^yO)^w4l42&`=Zdyh7a|!%9L}+^}+-}XU68i*rSBd+Hy{mGo#NG?m{uv^> z?aX^|*S{Bc!+UX`eJ}2F@5O!oy|^#D7x%^Y;(ovRzD({E9&il<=w;y=L0f<=NQ6#S z3#8E53SppM1Ysk53?!t39Q?oF5W*8i9*9C6n)e*DU#G+S5^{{To1DR7k!W~MZg#3T zGld+Mlb)&4XmRL_i(#w{18N~W7z^pApfhH|xB$9ciOitO%1cE@7$8m&2@Q}5!R-l( z{_KJ|DmWXIo+A`m2!FHW$#i-cbYnS?KU}Ub$RJz}VRfldhwx|*6-Y-R7)QIOKo13k zuoHrnW*Ehu0;w5JK(M@61Kll-WY)=xc)^BLg+6G z5QfIa#t73DrD}!I7?dNCjgsi)LXoyuC((=nz^pTB1?3gIM2g~moj1%(BpsK=82 zTcU;OFG}=o%`8s_knaq0JlTQkXxxF**1+fyM%cvM4xDre06V7x;B=(}=e-JAa5(_A zr#tE6i0q}vXw=2^>{(h`8mv;tg3*Lp(_b=JIsY8!G+r{ zH0UHUg)rz{BmTz?JBigvjvl#+LWN$TfkPEQD}zTe__{T66*@x;RhmD0!~d{ZCm9fE zwrdFR*Zu|s!-Ij{X#pS}o&*#=8xX@*KniAkZ<)*jxYYnK$o-Dl?ja26Wd2r$&w*9k zpdy1*Aj-`b%1rtaGYbV3RCr>rg_j)8pd0W4{_wOA1}`{zfdr5O`hjea4~Bq`fDCSC z#qbWJ6pRN`Kn0iu=7NP_DOd^Cf{kDc*amijz2E>i0#1N4;5@hr8bLF-4_d%8@EYEv z(J?N@$DA>D%m?d^g<&yRUn~X7#B#AA*a%FCX)qHu9-D^E!m6;P*lKJe_BFN}JAfU> z>aojM6Lt^#1$%`PI0qNtZn!Vr6OX|Y@eDj4{}5N=qw%qLIsPfW7_Y&(P)k|KUy>`oi>Cvk~WSuoAw!PD{Vimo_34Y3h#2A>D}pl=-G4$-AJ#X zFQI=y|DN7JZ>GOsuo>=*Fh(k4C_}@T%BW^+V(eoyFzzs3Gwql@%s6H?Q_dX2oWoqp z+`~N0Y-YY@@mPMWKCFD!NY)hAV%8ScVb(R)6E>Ue$&O_YV5``Z*^Aj z6nnXSx&0dZgZ8)h1mBA<;>-Bc_-pxx`ON~Fz)v6+j12nT@+nrcG=RUq04ipu1*O~3a3h^ ztxo5iUO5Y$Q=CUR&vV}CeBFiO66i9(rPO7G%VC!XT?Jixb(M9k?7FS%6<6Ff&^6a} zoa-9bldi39-Q3dLbZ$%B4!S+)=GZNv+sJMUy6x+B&zM4?)^ zSa?X-0x!%DG!zYFB3V$5YJwg$& zBH~gcKQb$FMr2*&uTjxa`lu~Y_oBU{M?^1=z8E8j$%&aAb1aq`n-n`Kc5m#+Mi4$rzKY=pGvV$8JMy(D2U$^m*wQ#cpDmc%!(bUv$3-{SIYtGV(H(X57pS%+zP@$-=YxWzEmJ*59YU zrvJ`tkS)%hpM5>YFK2Ylw*zPcat16La630NcYN;AJbvEryiIw}@{{uCO(Wh&vL!W=R;gIJT-qWN$rj1(%M;`a{07SwW$ZGw-z&sM-=bY z*lR{<4r#k-%e3{n?z#$HXE&TMY+32Ha@NXMt5mBltxj8ATjN_(y@s}?WXt^0MoW_{y^ z+zp2|MsM7>$#v75&G=^1=6hcZ`{K-($zSf;60~LYR>!Tgz5-vFzWVv=5no@}ma(mF zd(8GN-}rp9d^Yz776%{a%l~%f55?Zq7c| zzVdx--;e#ib-!-^g9FL~x9W!1T|GGX;JHHs4xKujaroGg)FTIvCLaC%Sf68ikH;O~ z{X@(TJ5NNNs682Za>tL6Kkhgcb*lDs^yyt^V$bZU?_IyIA->_j+2pfF&WX>RIN$$# z{e^)SE?pdUvGJ1p(%sAI%a5;=TzP$U@-^zU%IkL5t8cj8sQFjGzqU3;HGbEW+;sA0 z-p#AGWVi1BWccY#bNOxV?dm)3cQ)P)ySw*Z>b)~RfADkjeck=n4=Nt=9xi|6|LB{4 zC;az>~-e!JFI4DUhFS4JGe(7!zqR01U${K5rK@X%eshrwhs84MQu5Xs`QSZs6^ zL1)_7ayd2#CciSm)~FTya~O06;&{JgzKtNiGFopV{{JiE|KYEUzrLxdgr6Ayvk%dB G`M&^cYb`1O diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index af4f6dea8..da0fe6cb7 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -170,7 +170,7 @@ NOTE: In order to get Ocelot to view the scope claim from Okta properly, you hav `Issue 446 `_ that contains some code and examples that might help with Okta integration. Allowed Scopes -^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has at least one of the scopes in the list. diff --git a/docs/features/claimstransformation.rst b/docs/features/claimstransformation.rst index 07e949e80..77f38994b 100644 --- a/docs/features/claimstransformation.rst +++ b/docs/features/claimstransformation.rst @@ -15,7 +15,7 @@ Within this dictionary the entries specify how Ocelot should transform things! T The value of the entry is parsed to logic that will perform the transform. First of all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want to access the claims and get the CustomerId claim type. Next is a greater than (>) symbol which is just used to split the string. The next entry is either value or value with an indexer. If value is specified, Ocelot will just take the value and add it to the transform. If the value has an indexer, Ocelot will look for a delimiter which is provided after another greater than (>) symbol. Ocelot will then split the value on the delimiter and add whatever was at the index requested to the transform. Claims to Claims Transformation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Below is an example configuration that will transform claims to claims @@ -42,7 +42,7 @@ Below is an example configuration that will transform claims to headers This shows a transform where Ocelot looks at the users sub claim and transforms it into a CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". Claims to Query String Parameters Transformation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Below is an example configuration that will transform claims to query string parameters diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst index abab44aef..3a4bfd861 100644 --- a/docs/features/kubernetes.rst +++ b/docs/features/kubernetes.rst @@ -7,18 +7,21 @@ Ocelot will call the k8s endpoints API in a given namespace to get all of the en The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot. -``Install-Package Ocelot.Provider.Kubernetes`` +.. code-block:: powershell + + Install-Package Ocelot.Provider.Kubernetes Then add the following to your ConfigureServices method. .. code-block:: csharp - s.AddOcelot() - .AddKubernetes(); + s.AddOcelot().AddKubernetes(); + +If you have services deployed in kubernetes you will normally use the naming service to access them. +Default ``usePodServiceAccount = true``, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization: -If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization +.. code-block:: csharp -.. code-block::csharp public static class OcelotBuilderExtensions { public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true); @@ -27,57 +30,57 @@ If you have services deployed in kubernetes you will normally use the naming ser You can replicate a Permissive. Using RBAC role bindings. `Permissive RBAC Permissions `_, k8s api server and token will read from pod. -.. code-block::bash -kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts +.. code-block:: bash -The following example shows how to set up a Route that will work in kubernetes. The most important thing is the ServiceName which is made up of the kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. + kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts +The following example shows how to set up a Route that will work in kubernetes. The most important thing is the ServiceName which is made up of the kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration: .. code-block:: json { - "Routes": [ - { - "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/values", - "ServiceName": "downstreamservice", - "UpstreamHttpMethod": [ "Get" ] - } - ], - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "kube" + "Routes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/values", + "ServiceName": "downstreamservice", + "UpstreamHttpMethod": [ "Get" ] + } + ], + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "kube" + } + } } - } -} - -Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type. - Note: Hostใ€ Port and Token are no longer in useใ€‚ -You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration. +Service deployment in Namespace Dev, ServiceDiscoveryProvider type is kube, you also can set "pollkube" ServiceDiscoveryProvider type. +Note: Host, Port and Token are no longer in use. + +You use Ocelot to poll kubernetes for latest service information rather than per request. +If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration: .. code-block:: json "ServiceDiscoveryProvider": { - "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "pollkube", - "PollingInterval": 100 + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "pollkube", + "PollingInterval": 100 } The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. There is no way for Ocelot to work these out for you. -If your downstream service resides in a different namespace you can override the global setting at the Route level by specifying a ServiceNamespace. - +If your downstream service resides in a different namespace you can override the global setting at the Route level by specifying a ServiceNamespace: .. code-block:: json diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index af4067bbd..6ddc9984c 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -102,7 +102,7 @@ If you have multiple Routes with the same LoadBalancerOptions then all of those Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the moment but could be changed. Custom Load Balancers -^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^ `David Lievrouw `_ implemented a way to provide Ocelot with custom load balancer in `PR 1155 `_. diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 2c0e6961f..85271c30f 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -5,31 +5,31 @@ Ocelot supports proxying websockets with some extra bits. This functionality was In order to get websocket proxying working with Ocelot you need to do the following. -In your Configure method you need to tell your application to use WebSockets. +In your ``Configure`` method you need to tell your application to use WebSockets: .. code-block:: csharp - Configure(app => + Configure(app => { app.UseWebSockets(); app.UseOcelot().Wait(); }) -Then in your ocelot.json add the following to proxy a Route using websockets. +Then in your **ocelot.json** add the following to proxy a Route using WebSockets: .. code-block:: json - { - "DownstreamPathTemplate": "/ws", - "UpstreamPathTemplate": "/", - "DownstreamScheme": "ws", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 5001 - } - ], - } + { + "DownstreamPathTemplate": "/ws", + "UpstreamPathTemplate": "/", + "DownstreamScheme": "ws", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5001 + } + ], + } With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. @@ -48,7 +48,7 @@ In your Configure method you need to tell your application to use SignalR. .. code-block:: csharp - Configure(app => + Configure(app => { app.UseWebSockets(); app.UseOcelot().Wait(); @@ -58,22 +58,22 @@ Then in your ocelot.json add the following to proxy a Route using SignalR. Note .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/{catchAll}", - "DownstreamScheme": "ws", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 5001 - } - ], - "UpstreamPathTemplate": "/gateway/{catchAll}", - "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ] - } - ] -} + { + "Routes": [ + { + "DownstreamPathTemplate": "/{catchAll}", + "DownstreamScheme": "ws", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5001 + } + ], + "UpstreamPathTemplate": "/gateway/{catchAll}", + "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ] + } + ] + } With this configuration set Ocelot will match any SignalR traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. diff --git a/docs/make.bat b/docs/make.bat index cac625705..32bb24529 100755 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,64 +1,16 @@ @ECHO OFF +pushd %~dp0 + REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) +set SOURCEDIR=. set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul +%SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx @@ -67,215 +19,17 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Ocelot.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Ocelot.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) +if "%1" == "" goto help -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end +popd diff --git a/docs/make.sh b/docs/make.sh deleted file mode 100755 index cac625705..000000000 --- a/docs/make.sh +++ /dev/null @@ -1,281 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Ocelot.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Ocelot.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) - -:end diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..bd7d90223 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +# Defining the exact version will make sure things don't break +sphinx==7.2.6 +alabaster==0.7.13 \ No newline at end of file From 640b0051d44b5fa6b3c4d2a577e6798eca22a459 Mon Sep 17 00:00:00 2001 From: Raman Maksimchuk Date: Thu, 26 Oct 2023 14:07:55 +0300 Subject: [PATCH 2/4] #1731 Docs for 20.0 release (#1754) * Update README.md Review doc. Update by links. Add labels. * Update readme.md: Review doc. Update links * Update building.rst: Review doc * Update overview.rst * Update tests.rst: Review doc. Update links * Update releaseprocess.rst: Update development process * Update bigpicture.rst: Copy Welcoming from README * Update contributing.rst: Add discussions and dev process refs * Update gettingstarted.rst * Update gotchas.rst: Write IIS gotchas * Update notsupported.rst: Add sections. Use more links * Update administration.rst: Review doc. Fix markup. More links * Update authentication.rst: Review doc. Add more links * Update authorization.rst: review. Add "Authorization Middleware" section * Update caching.rst: Review markup. More links * Update claimstransformation.rst: Review doc. Update markdown. Middleware links * Correct spelling error in dev docs * Update configuration.rst: Review the doc. Rewrite "SSL Errors" section * Update delegatinghandlers.rst: Review. More links. Add "Order of Execution" section * Update dependencyinjection.rst: Review the doc. Fix markdown * Update errorcodes.rst: Review. Add "Design" section * Update graphql.rst: Review. Add "Future" section * Update headerstransformation.rst: Review. More links. Rewrite Placeholders & Future sections * Update headerstransformation.rst: Add "Global Headers Transformation" subsection * Update kubernetes.rst: Review. Links. Add K8s logo * Update kubernetes.rst: Add link to the parent feature * Update loadbalancer.rst: Fix markdown. More links * Update logging.rst: More links. Rewrite "Warning" section * Update methodtransformation.rst * Update middlewareinjection.rst: Review. Check links. Add "Future" section * Update qualityofservice.rst: Review markdown * Update ratelimiting.rst: Review. Markdown. Add "Future" section * Update requestaggregation.rst: Review. Fix markdown * Update requestid.rst: Review markdown * Update routing.rst: Review markdown. Simplify code blocks * Update servicediscovery.rst: Review markdown * Update servicefabric.rst: Review markdown * Update tracing.rst: Review markdown. More links * Update websockets.rst: Complete rewrite * Update README.md: Hide Coverage badge. More footnotes * Update README.md: Add "Service Discovery" footnote * Update index.rst: Arrange features. More instructions * Update releaseprocess.rst: Be more pleasant * Update qualityofservice.rst: Add QoS label and footnote * Fix Sphinx build errors & warning. Fix typos * Add bash script * Order feature names ascending * Sync & update scripts --- README.md | 102 ++--- docs/building/building.rst | 10 +- docs/building/overview.rst | 4 +- docs/building/releaseprocess.rst | 66 ++-- docs/building/tests.rst | 18 +- docs/conf.py | 7 +- docs/features/administration.rst | 73 ++-- docs/features/authentication.rst | 187 ++++------ docs/features/authorization.rst | 43 ++- docs/features/caching.rst | 49 ++- docs/features/claimstransformation.rst | 35 +- docs/features/configuration.rst | 353 ++++++++++-------- docs/features/delegatinghandlers.rst | 69 ++-- docs/features/dependencyinjection.rst | 75 ++-- docs/features/errorcodes.rst | 21 +- docs/features/graphql.rst | 29 +- docs/features/headerstransformation.rst | 173 +++++---- docs/features/kubernetes.rst | 109 +++--- docs/features/loadbalancer.rst | 276 +++++++------- docs/features/logging.rst | 14 +- docs/features/methodtransformation.rst | 31 +- docs/features/middlewareinjection.rst | 75 ++-- docs/features/qualityofservice.rst | 54 +-- docs/features/ratelimiting.rst | 77 ++-- docs/features/requestaggregation.rst | 207 +++++----- docs/features/requestid.rst | 45 ++- docs/features/routing.rst | 314 +++++++--------- docs/features/servicediscovery.rst | 318 ++++++++-------- docs/features/servicefabric.rst | 57 +-- docs/features/tracing.rst | 54 +-- docs/features/websockets.rst | 158 +++++--- docs/index.rst | 53 +-- docs/introduction/bigpicture.rst | 14 +- docs/introduction/contributing.rst | 6 +- docs/introduction/gettingstarted.rst | 18 +- docs/introduction/gotchas.rst | 23 +- docs/introduction/notsupported.rst | 34 +- docs/make.bat | 41 +- docs/make.sh | 25 ++ docs/readme.md | 20 +- .../IErrorsToHttpStatusCodeMapper.cs | 7 +- 41 files changed, 1844 insertions(+), 1500 deletions(-) create mode 100644 docs/make.sh diff --git a/README.md b/README.md index 3aa57d7e3..02385e021 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ [![CircleCI](https://circleci.com/gh/ThreeMammals/Ocelot/tree/main.svg?style=svg)](https://circleci.com/gh/ThreeMammals/Ocelot/tree/main) -[![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg)](https://coveralls.io/github/ThreeMammals/Ocelot) + -# Ocelot +## About Ocelot is a .NET API Gateway. This project is aimed at people using .NET running a microservices / service-oriented architecture -that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports. +that need a unified point of entry into their system. However it will work with anything that speaks HTTP(S) and run on any platform that ASP.NET Core supports. -In particular I want easy integration with IdentityServer reference and bearer tokens. - -We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. +In particular we want easy integration with [IdentityServer](https://github.com/IdentityServer) reference and [Bearer](https://oauth.net/2/bearer-tokens/) tokens. +We have been unable to find this in our current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. +We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. @@ -23,56 +23,74 @@ That is basically it with a bunch of other features! ## Features -A quick list of Ocelot's capabilities for more information see the [documentation](https://ocelot.readthedocs.io/en/latest/). - -* Routing -* Request Aggregation -* Service Discovery with Consul & Eureka -* Service Fabric -* Kubernetes -* WebSockets -* Authentication -* Authorization -* Rate Limiting -* Caching -* Retry policies / QoS -* Load Balancing -* Logging / Tracing / Correlation -* Headers / Method / Query String / Claims Transformation -* Custom Middleware / Delegating Handlers -* Configuration / Administration REST API -* Platform / Cloud Agnostic +A quick list of Ocelot's capabilities, for more information see the [Documentation](#documentation). + +* [Routing](https://ocelot.readthedocs.io/en/latest/features/routing.html) +* [Request Aggregation](https://ocelot.readthedocs.io/en/latest/features/requestaggregation.html) +* [GraphQL](https://ocelot.readthedocs.io/en/latest/features/graphql.html) [^1] +* [Service Discovery](https://ocelot.readthedocs.io/en/latest/features/servicediscovery.html) [^2] +* [Service Fabric](https://ocelot.readthedocs.io/en/latest/features/servicefabric.html) +* [Kubernetes](https://ocelot.readthedocs.io/en/latest/features/kubernetes.html) +* [Websockets](https://ocelot.readthedocs.io/en/latest/features/websockets.html) +* [Authentication](https://ocelot.readthedocs.io/en/latest/features/authentication.html) +* [Authorization](https://ocelot.readthedocs.io/en/latest/features/authorization.html) +* [Rate Limiting](https://ocelot.readthedocs.io/en/latest/features/ratelimiting.html) +* [Caching](https://ocelot.readthedocs.io/en/latest/features/caching.html) +* [Quality of Service](https://ocelot.readthedocs.io/en/latest/features/qualityofservice.html) [^3] +* [Load Balancer](https://ocelot.readthedocs.io/en/latest/features/loadbalancer.html) +* [Logging](https://ocelot.readthedocs.io/en/latest/features/logging.html) / [Tracing](https://ocelot.readthedocs.io/en/latest/features/tracing.html) / [Correlation](https://ocelot.readthedocs.io/en/latest/features/requestid.html) +* [Headers](https://ocelot.readthedocs.io/en/latest/features/headerstransformation.html) / [Method](https://ocelot.readthedocs.io/en/latest/features/methodtransformation.html) / [Query String](https://ocelot.readthedocs.io/en/latest/search.html?q=Query+String&check_keywords=yes&area=default) / [Claims](https://ocelot.readthedocs.io/en/latest/features/claimstransformation.html) Transformation +* [Custom Middleware](https://ocelot.readthedocs.io/en/latest/features/middlewareinjection.html) / [Delegating Handlers](https://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html) +* [Configuration](https://ocelot.readthedocs.io/en/latest/features/configuration.html) / [Administration](https://ocelot.readthedocs.io/en/latest/features/administration.html) REST API +* [Platform](https://ocelot.readthedocs.io/en/latest/building/building.html?highlight=Platform#building) & Cloud Agnostic [Building](https://ocelot.readthedocs.io/en/latest/building/building.html) ## Install -Ocelot is designed to work with ASP.NET and it targets `net7.0`. - -Install Ocelot and its dependencies using NuGet Package Manager: +Ocelot is designed to work with ASP.NET Core and it targets `net7.0` framework. +Install [Ocelot package](https://www.nuget.org/packages/Ocelot) and its dependencies using NuGet Package Manager: ```powershell Install-Package Ocelot ``` - Or via the .NET CLI: ```shell dotnet add package Ocelot ``` - -All versions can be found [here](https://www.nuget.org/packages/Ocelot/). +All versions can be found [on nuget](https://www.nuget.org/packages/Ocelot#versions-body-tab). ## Documentation - -Please click [here](https://ocelot.readthedocs.io/en/latest/) for the Ocelot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. +- [Ocelot documentation โ€” Read the Docs](https://ocelot.readthedocs.io) +
This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. +- [Ocelot RST Docs](https://github.com/ThreeMammals/Ocelot/tree/develop/docs) +
This includes source code of documentation as **.rst** files which are up to date for current development. ## Coming up - -You can see what we are working on [here](https://github.com/ThreeMammals/Ocelot/issues). +You can see what we are working on in [backlog](https://github.com/ThreeMammals/Ocelot/issues). ## Contributing -We love to receive contributions from the community so please keep them coming :) - -Pull requests, issues and commentary welcome! - -Please complete the relevant template for issues and PRs. Sometimes it's worth getting in touch with us to discuss changes before doing any work in case this is something we are already doing or it might not make sense. We can also give advice on the easiest way to do things :) - -Finally we mark all existing issues as help wanted, small, medium and large effort. If you want to contribute for the first time I suggest looking at a help wanted & small effort issue :) +We love to receive contributions from the community, so please keep them coming :octocat: +
Pull requests, issues and commentary welcome! + +Please complete the relevant [template](https://github.com/ThreeMammals/Ocelot/tree/main/.github) for [issues](https://github.com/ThreeMammals/Ocelot/blob/main/.github/ISSUE_TEMPLATE.md) and [PRs](https://github.com/ThreeMammals/Ocelot/blob/main/.github/PULL_REQUEST_TEMPLATE.md). +Sometimes it's worth getting in touch with us to [discuss](https://github.com/ThreeMammals/Ocelot/discussions) changes before doing any work in case this is something we are already doing or it might not make sense. +We can also give advice on the easiest way to do things :octocat: + +Finally, we mark all existing issues as [![label: help wanted][~helpwanted]](https://github.com/ThreeMammals/Ocelot/labels/help%20wanted) +[![label: small effort][~smalleffort]](https://github.com/ThreeMammals/Ocelot/labels/small%20effort) +[![label: medium effort][~mediumeffort]](https://github.com/ThreeMammals/Ocelot/labels/medium%20effort) +[![label: large effort][~largeeffort]](https://github.com/ThreeMammals/Ocelot/labels/large%20effort) [^4]. +
If you want to contribute for the first time, we suggest looking at a [![label: help wanted][~helpwanted]](https://github.com/ThreeMammals/Ocelot/labels/help%20wanted) +[![label: small effort][~smalleffort]](https://github.com/ThreeMammals/Ocelot/labels/small%20effort) +[![label: good first issue][~goodfirstissue]](https://github.com/ThreeMammals/Ocelot/labels/good%20first%20issue) :octocat: + +[~helpwanted]: https://img.shields.io/badge/-help%20wanted-128A0C.svg +[~smalleffort]: https://img.shields.io/badge/-small%20effort-fef2c0.svg +[~mediumeffort]: https://img.shields.io/badge/-medium%20effort-e0f42c.svg +[~largeeffort]: https://img.shields.io/badge/-large%20effort-10526b.svg +[~goodfirstissue]: https://img.shields.io/badge/-good%20first%20issue-ffc4d8.svg + +### Notes +[^1]: Ocelot doesnโ€™t directly support [GraphQL](https://graphql.org/). Developers can easily integrate the [GraphQL for .NET](/graphql-dotnet/graphql-dotnet) library. +[^2]: Ocelot does support [Consul](https://www.consul.io/), [Netflix Eureka](https://www.nuget.org/packages/Steeltoe.Discovery.Eureka), [Service Fabric](https://azure.microsoft.com/en-us/products/service-fabric/) service discovery providers, and special modes like [Dynamic Routing](/ThreeMammals/Ocelot/blob/main/docs/features/servicediscovery.rst#dynamic-routing) and [Custom Providers](/ThreeMammals/Ocelot/blob/main/docs/features/servicediscovery.rst#custom-providers). +[^3]: Retry policies only via [Polly](/App-vNext/Polly) library. +[^4]: See all [labels](https://github.com/ThreeMammals/Ocelot/issues/labels) of the repository. diff --git a/docs/building/building.rst b/docs/building/building.rst index 460380092..ec794e296 100644 --- a/docs/building/building.rst +++ b/docs/building/building.rst @@ -1,11 +1,13 @@ Building ======== -* You can also just run `dotnet tool restore && dotnet cake` locally!. Output will got to the `./artifacts` directory. +* You can also just run ``dotnet tool restore && dotnet cake`` locally! Output will got to the ``./artifacts`` directory. -* The best way to replicate the CI process is to build Ocelot locally is using the Dockerfile.build file which can be found in the docker folder in Ocelot root. +* The best way to replicate the CI process is to build Ocelot locally is using the `Dockerfile.build `_ file + which can be found in the `docker `_ folder in `Ocelot `_ root. Use the following command ``docker build --platform linux/amd64 -f ./docker/Dockerfile.build .`` for example. You will need to change the platform flag depending on your platform. -* There is a Makefile to make it easier to call the various targets in `build.cake`. The scripts are called with **.sh** but can be easily changed to **.ps1** if you are using Windows. +* There is a `Makefile `_ to make it easier to call the various targets in `build.cake `_. + The scripts are called with **.sh** but can be easily changed to **.ps1** if you are using Windows. -* Alternatively you can build the project in VS2022 with the latest .NET 7.0 SDK. +* Alternatively you can build the project in VS2022 with the latest `.NET 7.0 `_ SDK. diff --git a/docs/building/overview.rst b/docs/building/overview.rst index 70fdad0e7..4e3378673 100644 --- a/docs/building/overview.rst +++ b/docs/building/overview.rst @@ -1,4 +1,6 @@ Overview ======== -This document summarises the build and release process for the project. The build scripts are written using `Cake `_, and are defined in `./build.cake`. The scripts have been designed to be run by either developers locally or by a build server (currently `CircleCi `_), with minimal logic defined in the build server itself. \ No newline at end of file +This document summarises the build and release process for the project. +The build scripts are written using `Cake `_, and they are defined in ``./build.cake``. +The scripts have been designed to be run by either developers locally or by a build server (currently `CircleCi `_), with minimal logic defined in the build server itself. diff --git a/docs/building/releaseprocess.rst b/docs/building/releaseprocess.rst index b3746dd1b..ada142c39 100644 --- a/docs/building/releaseprocess.rst +++ b/docs/building/releaseprocess.rst @@ -1,38 +1,62 @@ -Release process +Release Process =============== -* The release process works best with Git Flow branching. -* Contributors can do whatever they want on PRs and merges to main will result in packages being released to GitHub and NuGet. +* The release process works best with `Gitflow `_ branching. +* Contributors can do whatever they want on PRs and feature branches to deliver a feature to **develop** branch. +* Maintainers can do whatever they want on PRs and merges to **main** will result in packages being released to GitHub and NuGet. Ocelot uses the following process to accept work into the NuGet packages. -1. User creates an issue or picks up an existing issue in GitHub. +1. User creates an issue or picks up an `existing issue `_ in GitHub. + An issue can be created by converting `discussion `_ topics if necessary and agreed upon. -2. User creates a fork and branches from this (unless a member of core team, they can just create a branch on the main repo) e.g. feat/xxx, fix/xxx etc. It doesn't really matter what the xxx is. It might make sense to use the issue number and maybe a short description. I don't care as long as it has (feat, fix, refactor)/xxx :) +2. User creates a fork and branches from this (unless a member of core team, they can just create a branch on the head repo) e.g. ``feature/xxx``, ``bug/xxx`` etc. + It doesn't really matter what the "xxx" is. It might make sense to use the issue number and maybe a short description. -3. When the user is happy with their work they can create a pull request against develop in GitHub with their changes. The user must follow the `SemVer `_ support for this is provided by `GitVersion `_. So if you need to make breaking changes please make sure you use the correct commit message so GitVersion uses the correct semver tags. Do not manually tag the Ocelot repo this will break things. +3. When the contributor is happy with their work they can create a pull request against **develop** in GitHub with their changes. -4. The Ocelot team will review the PR and if all is good merge it, else they will suggest feedback that the user will need to act on. In order to speed up getting a PR the user should think about the following. - - Have I covered all my changes with tests at unit and acceptance level? - - Have I updated any documentation that my changes may have affected? - - Does my feature make sense, have I checked all of Ocelot's other features to make sure it doesn't already exist? +4. The maintainer must follow the `SemVer `_ support for this is provided by `GitVersion `_. + So if the maintainer needs to make breaking changes, be sure to use the correct commit message, so **GitVersion** uses the correct **SemVer** tags. + Do not manually tag the Ocelot repo: this will break things! -In order for a PR to be merged the following must have occured. - - All new code is covered by unit tests. - - All new code has at least 1 acceptance test covering the happy path. - - Tests must have passed. - - Build must not have slowed down dramatically. - - The main Ocelot package must not have taken on any non MS dependencies. +5. The Ocelot team will review the PR and if all is good merge it, else they will suggest feedback that the user will need to act on. -5. After the PR is merged to develop the Ocelot NuGet packages will not be updated until a release is created. + In order to speed up getting a PR the contributor should think about the following: -6. When enough work has been completed to justify a new release. Develop will be merged into main the release process will begin which builds the code, versions it, pushes artifacts to GitHub and NuGet packages to NuGet. + - Have I covered all my changes with tests at unit and acceptance level? + - Have I updated any documentation that my changes may have affected? + - Does my feature make sense, have I checked all of Ocelot's other features to make sure it doesn't already exist? -7. The final step is to go back to GitHub and close any issues that are now fixed. You should see something like this in`GitHub `_ and this in `NuGet `_. + In order for a PR to be merged the following must have occured: + + - All new code is covered by unit tests. + - All new code has at least 1 acceptance test covering the happy path. + - Tests must have passed locally. + - Build must have green status. + - Build must not have slowed down dramatically. + - The main Ocelot package must not have taken on any non MS dependencies. + +6. After the PR is merged to **develop** the Ocelot NuGet packages will not be updated until a release is created. + +7. When enough work has been completed to justify a new release, + **develop** branch will be merged into **main** as **release/xxx** branch, the release process will begin which builds the code, versions it, pushes artifacts to GitHub and NuGet packages to NuGet. + +8. The final step is to go back to GitHub and close any issues that are now fixed. + **Note**: All linked issues to the PR in **Development** settings (right side PR settings) will be closed automatically while merging the PR. + It is imperative that developer uses the "**Link an issue from this repository**" pop-up dialog of the **Development** settings! Notes ----- -All NuGet package builds & releases are done with CircleCI `here _` and all releases are done from `here _`. +All NuGet package builds and releases are done with CircleCI, see `Pipelines - ThreeMammals/Ocelot `_. + +Only Tom Pallister (owner) and Ocelot Core Team members (maintainers) can merge releases into **main** at the moment. +This is to ensure there is a final `quality gate <#quality-gates>`_ in place. Tom is mainly looking for security issues on the final merge. + +We **do** follow this development and release process! +If anything is unclear or you get stuck in the process, please contact the `Ocelot Core Team `_ members or repository maintainers. + +Quality Gates +------------- -Only TomPallister can merge releases into main at the moment. This is to ensure there is a final quality gate in place. Tom is mainly looking for security issues on the final merge. + To be developed... diff --git a/docs/building/tests.rst b/docs/building/tests.rst index c21e91cfd..177667945 100644 --- a/docs/building/tests.rst +++ b/docs/building/tests.rst @@ -1,18 +1,14 @@ Tests ===== -The tests should all just run and work as part of the build process. You can of course also run them in visual studio. - +The tests should all just run and work as part of the build process. You can of course also run them in Visual Studio. Create SSL Cert for Testing -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can do this via openssl: - -Install openssl package (if you are using Windows, download binaries here). - -Generate private key: `openssl genrsa 2048 > private.pem` +--------------------------- -Generate the self signed certificate: `openssl req -x509 -days 1000 -new -key private.pem -out public.pem` +You can do this via `OpenSSL `_: -If needed, create PFX: `openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx` \ No newline at end of file +* Install `openssl package `_ (if you are using Windows, download binaries `here `_). +* Generate private key: ``openssl genrsa 2048 > private.pem`` +* Generate the self-signed certificate: ``openssl req -x509 -days 1000 -new -key private.pem -out public.pem`` +* If needed, create PFX: ``openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx`` diff --git a/docs/conf.py b/docs/conf.py index b950954c6..c8dbe77bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,9 +7,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'Ocelot' -copyright = ' 2023, ThreeMammals Ocelot team' +copyright = ' 2023 ThreeMammals Ocelot team' author = 'Tom Pallister, Ocelot Core team at ThreeMammals and Ocelot GitHub community' -release = '20.0.1' +release = '20.0.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -24,5 +24,8 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_theme html_theme = 'alabaster' + +# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_static_path html_static_path = ['_static'] diff --git a/docs/features/administration.rst b/docs/features/administration.rst index f55f0d627..cadecefdf 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -3,7 +3,7 @@ Administration Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own IdentityServer. -The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package: +The first thing you need to do if you want to use the administration API is bring in the relavent `NuGet package `_: .. code-block:: powershell @@ -39,21 +39,24 @@ After that we must pass these options to ``AddAdministration()`` extension of th You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API. -This feature was implemented for `Issue 228 `_. It is useful because the IdentityServer authentication middleware needs the URL of the IdentityServer. -If you are using the internal IdentityServer it might not always be possible to have the Ocelot URL. +This feature was implemented for `Issue 228 `_. +It is useful because the IdentityServer authentication middleware needs the URL of the IdentityServer. +If you are using the internal IdentityServer, it might not always be possible to have the Ocelot URL. Internal IdentityServer ----------------------- -The API is authenticated using Bearer tokens that you request from Ocelot itself. This is provided by the amazing `Identity Server `_ project that I have been using for a few years now. Check them out. +The API is authenticated using Bearer tokens that you request from Ocelot itself. +This is provided by the amazing `Identity Server `_ project that the .NET community has been using for several years. +Check them out. -In order to enable the administration section you need to do a few things. First of all add this to your initial **Startup.cs**. +In order to enable the administration section, you need to do a few things. First of all, add this to your initial **Startup.cs**. The path can be anything you want and it is obviously recommended don't use a URL you would like to route through with Ocelot as this will not work. The administration uses the ``MapWhen`` functionality of ASP.NET Core and all requests to "**{root}/administration**" will be sent there not to the Ocelot middleware. The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! -In order to pass this secret string as parameter we must call the ``AddAdministration()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: +In order to pass this secret string as parameter, we must call the ``AddAdministration()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: .. code-block:: csharp @@ -65,8 +68,8 @@ In order to pass this secret string as parameter we must call the ``AddAdministr } In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. -This means that you need to add the base URL of Ocelot to global configuration if it is not default (http://localhost:5000). -Please note, if you are using something like Docker to host Ocelot it might not be able to call back to **localhost** etc and you need to know what you are doing with Docker networking in this scenario. +This means that you need to add the base URL of Ocelot to global configuration if it is not default ``http://localhost:5000``. +Please note, if you are using something like Docker to host Ocelot it might not be able to call back to **localhost** etc, and you need to know what you are doing with Docker networking in this scenario. Anyway, this can be done as follows. If you want to run on a different host and port locally: @@ -85,19 +88,17 @@ or if Ocelot is exposed via DNS: "BaseUrl": "http://mydns.com" } -Now if you went with the configuration options above and want to access the API you can use the Postman scripts called **ocelot.postman_collection.json** in the solution to change the Ocelot configuration. -Obviously these will need to be changed if you are running Ocelot on a different URL to http://localhost:5000. +Now, if you went with the configuration options above and want to access the API, you can use the Postman scripts called **ocelot.postman_collection.json** in the solution to change the Ocelot configuration. +Obviously these will need to be changed if you are running Ocelot on a different URL to ``http://localhost:5000``. The scripts show you how to request a Bearer token from Ocelot and then use it to GET the existing configuration and POST a configuration. If you are running multiple Ocelot instances in a cluster then you need to use a certificate to sign the Bearer tokens used to access the administration API. -In order to do this you need to add two more environmental variables for each Ocelot in the cluster. +In order to do this, you need to add two more environmental variables for each Ocelot in the cluster: -``OCELOT_CERTIFICATE`` - The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. -``OCELOT_CERTIFICATE_PASSWORD`` - The password for the certificate. +1. ``OCELOT_CERTIFICATE`` The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. +2. ``OCELOT_CERTIFICATE_PASSWORD`` The password for the certificate. Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelot instances in the cluster have the same certificate then you are good! @@ -105,39 +106,43 @@ If all the other Ocelot instances in the cluster have the same certificate then Administration API ------------------ -**POST {adminPath}/connect/token** +POST {adminPath}/connect/token +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. +This gets a token for use with the admin area using the client credentials we talk about setting above. +Under the hood this calls into an IdentityServer hosted within Ocelot. -The body of the request is form-data as follows +The body of the request is form-data as follows: -``client_id`` set as admin - -``client_secret`` set as whatever you used when setting up the administration services. - -``scope`` set as admin - -``grant_type`` set as client_credentials - -**GET {adminPath}/configuration** +* ``client_id`` set as admin +* ``client_secret`` set as whatever you used when setting up the administration services. +* ``scope`` set as admin +* ``grant_type`` set as client_credentials +GET {adminPath}/configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. -**POST {adminPath}/configuration** +POST {adminPath}/configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This overwrites the existing configuration (should probably be a put!). I recommend getting your config from the GET endpoint, making any changes and posting it back...simples. +This overwrites the existing configuration (should probably be a PUT!). +We recommend getting your config from the GET endpoint, making any changes and posting it back... simples. -The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up Ocelot on a file system. +The body of the request is JSON and it is the same format as the `FileConfiguration `_ +that we use to set up Ocelot on a file system. -Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk where your **ocelot.json** or **ocelot.{environment}.json** is located. +Please note, if you want to use this API then the process running Ocelot must have permission to write to the disk where your **ocelot.json** or **ocelot.{environment}.json** is located. This is because Ocelot will overwrite them on save. -**DELETE {adminPath}/outputcache/{region}** +DELETE {adminPath}/outputcache/{region} +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. +This clears a region of the cache. If you are using a backplane, it will clear all instances of the cache! +Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time, so just use a distributed cache. -The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. +The region is whatever you set against the **Region** field in the `FileCacheOptions `_ section of the Ocelot configuration. """" diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index da0fe6cb7..3b2398e8d 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -1,65 +1,54 @@ Authentication ============== -In order to authenticate Routes and subsequently use any of Ocelot's claims based features such as authorization or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. +In order to authenticate Routes and subsequently use any of Ocelot's claims based features such as authorization or modifying the request with values from the token, +users must register authentication services in their **Startup.cs** as usual but they provide a scheme (authentication provider key) with each registration e.g. .. code-block:: csharp public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - }); + services + .AddAuthentication() + .AddJwtBearer(authenticationProviderKey, + options => { /* custom auth-setup */ }); } - -In this example TestKey is the scheme that this provider has been registered with. We then map this to a Route in the configuration e.g. +In this example "**TestKey**" is the scheme that this provider has been registered with. We then map this to a Route in the configuration e.g. .. code-block:: json - "Routes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "RouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -When Ocelot runs it will look at this Routes AuthenticationOptions.AuthenticationProviderKey and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot will not start up, if there is then the Route will use that provider when it executes. - -If a Route is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. + "Routes": [{ + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +When Ocelot runs it will look at this Routes ``AuthenticationOptions.AuthenticationProviderKey`` and check that there is an authentication provider registered with the given key. +If there isn't then Ocelot will not start up. If there is then the Route will use that provider when it executes. + +If a Route is authenticated, Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. +If the request fails authentication, Ocelot returns a HTTP status code `401 Unauthorized `_. JWT Tokens -^^^^^^^^^^ +---------- -If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. +If you want to authenticate using JWT tokens maybe from a provider like `Auth0 `_, you can register your authentication middleware as normal e.g. .. code-block:: csharp public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => + services + .AddAuthentication() + .AddJwtBearer(authenticationProviderKey, options => { - x.Authority = "test"; - x.Audience = "test"; + options.Authority = "test"; + options.Audience = "test"; }); - services.AddOcelot(); } @@ -67,45 +56,32 @@ Then map the authentication provider key to a Route in your configuration e.g. .. code-block:: json - "Routes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "RouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - - + "Routes": [{ + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] Identity Server Bearer Tokens -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +----------------------------- -In order to use IdentityServer bearer tokens, register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation. +In order to use `IdentityServer `_ bearer tokens, register your IdentityServer services as usual in ``ConfigureServices`` with a scheme (key). +If you don't understand how to do this, please consult the IdentityServer `documentation `_. .. code-block:: csharp public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; - Action options = o => - { - o.Authority = "https://whereyouridentityserverlives.com"; - // etc - }; - - services.AddAuthentication() + Action options = (opt) => + { + opt.Authority = "https://whereyouridentityserverlives.com"; + // ... + }; + services + .AddAuthentication() .AddJwtBearer(authenticationProviderKey, options); - services.AddOcelot(); } @@ -113,65 +89,56 @@ Then map the authentication provider key to a Route in your configuration e.g. .. code-block:: json - "Routes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "RouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -Okta -^^^^ -Add the following to your startup Configure method: + "Routes": [{ + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +Auth0 by Okta +------------- +Yet another identity provider by `Okta `_, see `Auth0 Developer Resources `_. -.. code-block:: csharp +Add the following to your startup ``Configure`` method: - app - .UseAuthentication() - .UseOcelot() - .Wait(); +.. code-block:: csharp + app.UseAuthentication() + .UseOcelot().Wait(); -Add the following, at minimum, to your startup ConfigureServices method: +Add the following, at minimum, to your startup ``ConfigureServices`` method: .. code-block:: csharp - services - .AddAuthentication() - .AddJwtBearer(oktaProviderKey, options => - { - options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience - options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier} - }); + services + .AddAuthentication() + .AddJwtBearer(oktaProviderKey, options => + { + options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience + options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier} + }); services.AddOcelot(configuration); - -NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope" - +**Note** In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta ``"scp"`` claim to ``"scope"``: .. code-block:: csharp - // Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp"); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope"); - + // Map Okta "scp" to "scope" claims instead of http://schemas.microsoft.com/identity/claims/scope to allow Ocelot to read/verify them + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp"); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope"); -`Issue 446 `_ that contains some code and examples that might help with Okta integration. +`Issue 446 `_ contains some code and examples that might help with Okta integration. Allowed Scopes -^^^^^^^^^^^^^^ +-------------- -If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has at least one of the scopes in the list. +If you add scopes to **AllowedScopes**, Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has at least one of the scopes in the list. This is a way to restrict access to a Route on a per scope basis. + +More identity providers +----------------------- + +We invite you to add more examples, if you have integrated with other identity providers and the integration solution is working. +Please, open `Show and tell `_ discussion in the repository. diff --git a/docs/features/authorization.rst b/docs/features/authorization.rst index 1510e131e..0e46ca4f0 100644 --- a/docs/features/authorization.rst +++ b/docs/features/authorization.rst @@ -1,7 +1,8 @@ Authorization ============= -Ocelot supports claims based authorization which is run post authentication. This means if you have a route you want to authorize you can add the following to your Route configuration. +Ocelot supports claims based authorization which is run post authentication. +This means if you have a route you want to authorize, you can add the following to your Route configuration: .. code-block:: json @@ -9,4 +10,42 @@ Ocelot supports claims based authorization which is run post authentication. Thi "UserType": "registered" } -In this example when the authorization middleware is called Ocelot will check to see if the user has the claim type UserType and if the value of that claim is registered. If it isn't then the user will not be authorized and the response will be 403 forbidden. +In this example, when the ``AuthorizationMiddleware`` is called, Ocelot will check to see if the user has the claim type **UserType** and if the value of that claim is ``"registered"``. +If it isn't then the user will not be authorized and the response will be `403 Forbidden `_. + +Authorization Middleware +------------------------ + +The `AuthorizationMiddleware `_ is built-in into Ocelot pipeline. + + | Previous private: ``ClaimsToClaimsMiddleware`` + | Previous public: ``PreAuthorizationMiddleware`` + | **This**: ``AuthorizationMiddleware`` + | Next private: ``ClaimsToHeadersMiddleware`` + | Next public: ``PreQueryStringBuilderMiddleware`` + +.. role:: htm(raw) + :format: html + +So, the closest middlewares are in order of calling: + +``ClaimsToClaimsMiddleware`` :htm:`→` ``PreAuthorizationMiddleware`` :htm:`→` **AuthorizationMiddleware** :htm:`→` ``ClaimsToHeadersMiddleware`` :htm:`→` ``PreQueryStringBuilderMiddleware`` + +As you may know from the :doc:`../features/middlewareinjection` section, the Authorization middleware can be overridden like this: + +.. code-block:: csharp + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + var configuration = new OcelotPipelineConfiguration + { + AuthorizationMiddleware = async (context, next) => + { + await next.Invoke(); + } + }; + app.UseOcelot(configuration); + } + +Do this in very rare cases, because overriding Authorization middleware means you will lose claims & scopes authorizer through the **RouteClaimsRequirement** property of the route. +Another option is preparing before the actual authorization in ``PreAuthorizationMiddleware`` which is public and open to overriding. diff --git a/docs/features/caching.rst b/docs/features/caching.rst index 0a1cac37b..f557d1817 100644 --- a/docs/features/caching.rst +++ b/docs/features/caching.rst @@ -1,48 +1,57 @@ Caching ======= -Ocelot supports some very rudimentary caching at the moment provider by the `CacheManager `_ project. This is an amazing project that is solving a lot of caching problems. I would recommend using this package to cache with Ocelot. +Ocelot supports some very rudimentary caching at the moment provider by the `CacheManager `_ project. +This is an amazing project that is solving a lot of caching problems. We would recommend using this package to cache with Ocelot. -The following example shows how to add CacheManager to Ocelot so that you can do output caching. +The following example shows how to add **CacheManager** to Ocelot so that you can do output caching. -First of all add the following NuGet package. +First of all, add the following `NuGet package `_: - ``Install-Package Ocelot.Cache.CacheManager`` +.. code-block:: powershell + + Install-Package Ocelot.Cache.CacheManager This will give you access to the Ocelot cache manager extension methods. -The second thing you need to do something like the following to your ConfigureServices.. +The second thing you need to do something like the following to your ``ConfigureServices`` method: .. code-block:: csharp using Ocelot.Cache.CacheManager; - s.AddOcelot() - .AddCacheManager(x => - { - x.WithDictionaryHandle(); - }) + ConfigureServices(services => + { + services.AddOcelot() + .AddCacheManager(x => x.WithDictionaryHandle()); + }); -Finally in order to use caching on a route in your Route configuration add this setting. +Finally, in order to use caching on a route in your Route configuration add this setting: .. code-block:: json "FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" } -In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. +In this example **TtlSeconds** is set to 15 which means the cache will expire after 15 seconds. +The **Region** represents a region of caching. -If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot AddCacheManager configuration method. You can use any settings supported by the CacheManager package and just pass them in. +If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot ``AddCacheManager`` configuration method. +You can use any settings supported by the **CacheManager** package and just pass them in. -Anyway Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache. You can also clear the cache for a region by calling Ocelot's administration API. +Anyway, Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache. You can also clear the cache for a region by calling Ocelot's administration API. -Your own caching -^^^^^^^^^^^^^^^^ +Your Own Caching +---------------- -If you want to add your own caching method implement the following interfaces and register them in DI e.g. ``services.AddSingleton, MyCache>()`` +If you want to add your own caching method, implement the following interfaces and register them in DI e.g. -``IOcelotCache`` this is for output caching. +.. code-block:: csharp -``IOcelotCache`` this is for caching the file configuration if you are calling something remote to get your config such as Consul. + services.AddSingleton, MyCache>(); -Please dig into the Ocelot source code to find more. I would really appreciate it if anyone wants to implement Redis, memcache etc.. +* ``IOcelotCache`` this is for output caching. +* ``IOcelotCache`` this is for caching the file configuration if you are calling something remote to get your config such as Consul. +Please dig into the Ocelot source code to find more. +We would really appreciate it if anyone wants to implement `Redis `_, `Memcached `_ etc. +Please, open a new `Show and tell `_ thread in `Discussions `_ space of the repository. diff --git a/docs/features/claimstransformation.rst b/docs/features/claimstransformation.rst index 77f38994b..9cc1d7df6 100644 --- a/docs/features/claimstransformation.rst +++ b/docs/features/claimstransformation.rst @@ -3,16 +3,27 @@ Claims Transformation Ocelot allows the user to access claims and transform them into headers, query string parameters, other claims and change downstream paths. This is only available once a user has been authenticated. -After the user is authenticated, we run the claims to claims transformation middleware. This allows the user to transform claims before the authorisation middleware is called. -After the user is authorized, we call the claims to headers middleware, then the claims to query string parameters middleware, and finally the claims to downstream path middleware. +After the user is authenticated, we run the claims to claims transformation middleware (see the `ClaimsToClaimsMiddleware `_ class). +This allows the user to transform claims before the authorization middleware is called. +After the user is authorized, we call the claims to headers middleware (see the `ClaimsToHeadersMiddleware `_ class), +then the claims to query string parameters middleware (see the `ClaimsToQueryStringMiddleware `_ class), +and finally the claims to downstream path middleware (see the `ClaimsToDownstreamPathMiddleware `_ class). -The syntax for performing the transforms is the same for each process. In the Route configuration, a json dictionary is added with a specific name either AddClaimsToRequest, AddHeadersToRequest, AddQueriesToRequest, or ChangeDownstreamPathTemplate. +The syntax for performing the transforms is the same for each process. +In the Route configuration, a JSON dictionary is added with a specific name either **AddClaimsToRequest**, **AddHeadersToRequest**, **AddQueriesToRequest**, or **ChangeDownstreamPathTemplate**. -Note: I'm not a hotshot programmer so have no idea if this syntax is good... +Note: This syntax is not ideal. So any suggestions are welcome... -Within this dictionary the entries specify how Ocelot should transform things! The key to the dictionary is going to become the key of either a claim, header or query parameter. In the case of ChangeDownstreamPathTemplate, the key must be also specified in the DownstreamPathTemplate, in order to do the transformation. +Within this dictionary the entries specify how Ocelot should transform things! +The key to the dictionary is going to become the key of either a claim, header or query parameter. +In the case of **ChangeDownstreamPathTemplate**, the key must be also specified in the **DownstreamPathTemplate**, in order to do the transformation. -The value of the entry is parsed to logic that will perform the transform. First of all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want to access the claims and get the CustomerId claim type. Next is a greater than (>) symbol which is just used to split the string. The next entry is either value or value with an indexer. If value is specified, Ocelot will just take the value and add it to the transform. If the value has an indexer, Ocelot will look for a delimiter which is provided after another greater than (>) symbol. Ocelot will then split the value on the delimiter and add whatever was at the index requested to the transform. +The value of the entry is parsed to logic that will perform the transform. +First of all, a dictionary accessor is specified e.g. ``Claims[CustomerId]``. This means we want to access the claims and get the ``CustomerId`` claim type. +Next is a "greater than" ``>`` symbol which is just used to split the string. The next entry is either value or value with an indexer. +If value is specified, Ocelot will just take the value and add it to the transform. +If the value has an indexer, Ocelot will look for a delimiter which is provided after another "greater than" ``>`` symbol. +Ocelot will then split the value on the delimiter and add whatever was at the index requested to the transform. Claims to Claims Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -26,7 +37,7 @@ Below is an example configuration that will transform claims to claims "UserId": "Claims[sub] > value[1] > |" } -This shows a transforms where Ocelot looks at the users sub claim and transforms it into UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". +This shows a transforms where Ocelot looks at the users ``sub`` claim and transforms it into **UserType** and **UserId** claims. Assuming the ``sub`` looks like this ``usertypevalue|useridvalue``. Claims to Headers Tranformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -39,7 +50,7 @@ Below is an example configuration that will transform claims to headers "CustomerId": "Claims[sub] > value[1] > |" } -This shows a transform where Ocelot looks at the users sub claim and transforms it into a CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". +This shows a transform where Ocelot looks at the users ``sub`` claim and transforms it into a **CustomerId** header. Assuming the ``sub`` looks like this ``usertypevalue|useridvalue``. Claims to Query String Parameters Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,7 +63,7 @@ Below is an example configuration that will transform claims to query string par "LocationId": "Claims[LocationId] > value", } -This shows a transform where Ocelot looks at the users LocationId claim and add it as a query string parameter to be forwarded onto the downstream service. +This shows a transform where Ocelot looks at the users ``LocationId`` claim and add it as a query string parameter to be forwarded onto the downstream service. Claims to Downstream Path Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -67,7 +78,7 @@ Below is an example configuration that will transform claims to downstream path "userId": "Claims[sub] > value[1] > |", } -This shows a transform where Ocelot looks at the users userId claim and substitutes the value to the "{userId}" placeholder specified in the DownstreamPathTemplate. Take into account that the key specified in the ChangeDownstreamPathTemplate must be the same than the placeholder specified in -the DownstreamPathTemplate. +This shows a transform where Ocelot looks at the users ``userId`` claim and substitutes the value to the "{userId}" placeholder specified in the **DownstreamPathTemplate**. +Take into account that the key specified in the **ChangeDownstreamPathTemplate** must be the same than the placeholder specified in the **DownstreamPathTemplate**. -Note: if a key specified in the ChangeDownstreamPathTemplate does not exist as a placeholder in DownstreamPathTemplate it will fail at runtime returning an error in the response. +Note: If a key specified in the **ChangeDownstreamPathTemplate** does not exist as a placeholder in **DownstreamPathTemplate**, it will fail at runtime returning an error in the response. diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 2b601a555..feec24721 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -1,83 +1,78 @@ Configuration ============= -An example configuration can be found `here `_. There are two sections to the configuration. An array of Routes and a GlobalConfiguration. The Routes are the objects that tell Ocelot how to treat an upstream request. The Global configuration is a bit hacky and allows overrides of Route specific settings. It's useful if you don't want to manage lots of Route specific settings. +An example configuration can be found here in `ocelot.json `_. +There are two sections to the configuration: an array of **Routes** and a **GlobalConfiguration**: + +* The **Routes** are the objects that tell Ocelot how to treat an upstream request. +* The **GlobalConfiguration** is a bit hacky and allows overrides of Route specific settings. It's useful if you do not want to manage lots of Route specific settings. .. code-block:: json - { - "Routes": [], - "GlobalConfiguration": {} - } + { + "Routes": [], + "GlobalConfiguration": {} + } -Here is an example Route configuration, You don't need to set all of these things but this is everything that is available at the moment: +Here is an example Route configuration. You don't need to set all of these things but this is everything that is available at the moment: .. code-block:: json { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamHttpMethod": "", - "DownstreamHttpVersion": "", - "AddHeadersToRequest": {}, - "AddClaimsToRequest": {}, - "RouteClaimsRequirement": {}, - "AddQueriesToRequest": {}, - "RequestIdKey": "", - "FileCacheOptions": { - "TtlSeconds": 0, - "Region": "" - }, - "SecurityOptions": { - "IPAllowedList": [], - "IPBlockedList": [] - }, - "RouteIsCaseSensitive": false, - "ServiceName": "", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 0, - "DurationOfBreak": 0, - "TimeoutValue": 0 - }, - "LoadBalancer": "", - "RateLimitOptions": { - "ClientWhitelist": [], - "EnableRateLimiting": false, - "Period": "", - "PeriodTimespan": 0, - "Limit": 0 - }, - "AuthenticationOptions": { - "AuthenticationProviderKey": "", - "AllowedScopes": [] - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true, - "UseTracing": true, - "MaxConnectionsPerServer": 100 - }, - "DangerousAcceptAnyServerCertificateValidator": false, - "SecurityOptions": { - "IPAllowedList": [], - "IPBlockedList": [], - "ExcludeAllowedFromBlocked": false - } - } + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ], + "DownstreamHttpMethod": "", + "DownstreamHttpVersion": "", + "AddHeadersToRequest": {}, + "AddClaimsToRequest": {}, + "RouteClaimsRequirement": {}, + "AddQueriesToRequest": {}, + "RequestIdKey": "", + "FileCacheOptions": { + "TtlSeconds": 0, + "Region": "europe-central" + }, + "RouteIsCaseSensitive": false, + "ServiceName": "", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 51876 } + ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 0, + "DurationOfBreak": 0, + "TimeoutValue": 0 + }, + "LoadBalancer": "", + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": false, + "Period": "", + "PeriodTimespan": 0, + "Limit": 0 + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "", + "AllowedScopes": [] + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true, + "UseTracing": true, + "MaxConnectionsPerServer": 100 + }, + "DangerousAcceptAnyServerCertificateValidator": false, + "SecurityOptions": { + "IPAllowedList": [], + "IPBlockedList": [], + "ExcludeAllowedFromBlocked": false + } + } More information on how to use these options is below. -Multiple environments +Multiple Environments --------------------- Like any other ASP.NET Core project Ocelot supports configuration file names such as **configuration.dev.json**, **configuration.test.json** etc. In order to implement this add the following @@ -85,104 +80,107 @@ to you: .. code-block:: csharp - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") - .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") - .AddEnvironmentVariables(); - }) + ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json") + .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") + .AddEnvironmentVariables(); + }) Ocelot will now use the environment specific configuration and fall back to **ocelot.json** if there isn't one. You also need to set the corresponding environment variable which is ``ASPNETCORE_ENVIRONMENT``. More info on this can be found in the ASP.NET Core docs: `Use multiple environments in ASP.NET Core `_. -Merging configuration files +Merging Configuration Files --------------------------- -This feature was requested in `Issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier. +This feature was requested in `issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier. -Instead of adding the configuration directly e.g. ``AddJsonFile("ocelot.json")`` you can call ``AddOcelot()`` like below. +Instead of adding the configuration directly e.g. ``AddJsonFile("ocelot.json")`` you can call ``AddOcelot()`` like below: .. code-block:: csharp - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddOcelot(hostingContext.HostingEnvironment) - .AddEnvironmentVariables(); - }) + ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot(hostingContext.HostingEnvironment) + .AddEnvironmentVariables(); + }) -In this scenario Ocelot will look for any files that match the pattern ``(?i)ocelot.([a-zA-Z0-9]*).json`` and then merge these together. If you want to set the GlobalConfiguration property you must have a file called **ocelot.global.json**. +In this scenario Ocelot will look for any files that match the pattern ``(?i)ocelot.([a-zA-Z0-9]*).json`` and then merge these together. +If you want to set the **GlobalConfiguration** property, you must have a file called **ocelot.global.json**. -The way Ocelot merges the files is basically load them, loop over them, add any Routes, add any AggregateRoutes and if the file is called **ocelot.global.json** add the GlobalConfiguration aswell as any Routes or AggregateRoutes. -Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. +The way Ocelot merges the files is basically load them, loop over them, add any Routes, add any **AggregateRoutes** and if the file is called **ocelot.global.json** add the **GlobalConfiguration** aswell as any Routes or **AggregateRoutes**. +Ocelot will then save the merged configuration to a file called **ocelot.json** and this will be used as the source of truth while Ocelot is running. -At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. -I would advise always checking what is in **ocelot.json** file if you have any problems. +At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. +This is something to be aware of when you are investigating problems. +We would advise always checking what is in **ocelot.json** file if you have any problems. -You can also give Ocelot a specific path to look in for the configuration files like below. +You can also give Ocelot a specific path to look in for the configuration files like below: .. code-block:: csharp - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddOcelot("/foo/bar", hostingContext.HostingEnvironment) - .AddEnvironmentVariables(); - }) + ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot("/foo/bar", hostingContext.HostingEnvironment) + .AddEnvironmentVariables(); + }) -Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm. +Ocelot needs the ``HostingEnvironment`` so it knows to exclude anything environment specific from the algorithm. -Store configuration in Consul +Store Configuration in Consul ----------------------------- -The first thing you need to do is install the NuGet package that provides Consul support in Ocelot. +The first thing you need to do is install the `NuGet package `_ that provides `Consul `_ support in Ocelot. .. code-block:: powershell Install-Package Ocelot.Provider.Consul Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in Consul KV store. -In order to register Consul services we must call the ``AddConsul()`` and ``AddConfigStoredInConsul()`` extensions using the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: +In order to register Consul services we must call the ``AddConsul()`` and ``AddConfigStoredInConsul()`` extensions using the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: .. code-block:: csharp - services - .AddOcelot() - .AddConsul() - .AddConfigStoredInConsul(); + services + .AddOcelot() + .AddConsul() + .AddConfigStoredInConsul(); You also need to add the following to your **ocelot.json**. This is how Ocelot finds your Consul agent and interacts to load and store the configuration from Consul. .. code-block:: json - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500 - } + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500 } + } -I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! -I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. +The team decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. +Why not take advantage of the fact Consul already gives you this! +We guess it means if you want to use Ocelot to its fullest, you take on Consul as a dependency for now. -This feature has a **3** seconds TTL cache before making a new request to your local Consul agent. +This feature has a `3 `_ seconds TTL cache before making a new request to your local Consul agent. -Reload JSON config on change +Reload JSON Config On Change ---------------------------- -Ocelot supports reloading the json configuration file on change. For instance, the following will recreate Ocelot's internal configuration when the **ocelot.json** file is updated manually: +Ocelot supports reloading the JSON configuration file on change. For instance, the following will recreate Ocelot internal configuration when the **ocelot.json** file is updated manually: .. code-block:: csharp @@ -191,57 +189,101 @@ Ocelot supports reloading the json configuration file on change. For instance, t Configuration Key ----------------- -If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) -This feature was requested in `Issue 346 `_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g. +If you are using Consul for configuration (or other providers in the future), you might want to key your configurations: so you can have multiple configurations. +This feature was requested in `issue 346 `_. +In order to specify the key you need to set the **ConfigurationKey** property in the **ServiceDiscoveryProvider** options of the configuration JSON file e.g. .. code-block:: json - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500, - "ConfigurationKey": "Oceolot_A" - } + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500, + "ConfigurationKey": "Ocelot_A" } + } -In this example Ocelot will use "Oceolot_A" as the key for your configuration when looking it up in Consul. - -If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key. +In this example Ocelot will use ``Ocelot_A`` as the key for your configuration when looking it up in Consul. +If you do not set the **ConfigurationKey**, Ocelot will use the string ``InternalConfiguration`` as the key. -Follow Redirects / Use CookieContainer --------------------------------------- +Follow Redirects aka HttpHandlerOptions +--------------------------------------- -Use HttpHandlerOptions in Route configuration to set up HttpHandler behavior: + Class: `FileHttpHandlerOptions `_ -1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is ``false``. +Use ``HttpHandlerOptions`` in a Route configuration to set up ``HttpHandler`` behavior: -2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests. The default value is ``false``. -Please note that if you are using the CookieContainer Ocelot caches the ``HttpClient`` for each downstream service. This means that all requests to that DownstreamService will share the same cookies. -`Issue 274 `_ was created because a user noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. -If you don't cache the clients that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight requests. -This would also mean that subsequent requests don't use the cookies from the previous response! All in all not a great situation. I would avoid setting UseCookieContainer to ``true`` unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! - -MaxConnectionsPerServer property -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: json -This controls how many connections the internal HttpClient will open. This can be set at Route or global level. + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + "UseCookieContainer": false, + "UseTracing": true, + "MaxConnectionsPerServer": 100 + }, + +* **AllowAutoRedirect** is a value that indicates whether the request should follow redirection responses. + Set it ``true`` if the request should automatically follow redirection responses from the downstream resource; otherwise ``false``. + The default value is ``false``. + +* **UseCookieContainer** is a value that indicates whether the handler uses the ``CookieContainer`` property to store server cookies and uses these cookies when sending requests. + The default value is ``false``. + Please note, if you use the ``CookieContainer``, Ocelot caches the ``HttpClient`` for each downstream service. + This means that all requests to that downstream service will share the same cookies. + `Issue 274 `_ was created because a user noticed that the cookies were being shared. + The Ocelot team tried to think of a nice way to handle this but we think it is impossible. + If you don't cache the clients, that means each request gets a new client and therefore a new cookie container. + If you clear the cookies from the cached client container, you get race conditions due to inflight requests. + This would also mean that subsequent requests don't use the cookies from the previous response! + All in all not a great situation. + We would avoid setting **UseCookieContainer** to ``true`` unless you have a really really good reason. + Just look at your response headers and forward the cookies back with your next request! + +* **MaxConnectionsPerServer** This controls how many connections the internal ``HttpClient`` will open. This can be set at Route or global level. SSL Errors ---------- -If you want to ignore SSL warnings / errors set the following in your Route config. +If you want to ignore SSL warnings (errors), set the following in your Route config: .. code-block:: json "DangerousAcceptAnyServerCertificateValidator": true -I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. +**We don't recommend doing this!** +The team suggests creating your own certificate and then getting it trusted by your local (remote) machine, if you can. +For ``https`` scheme this fake validator was requested by `issue 309 `_. +For ``wss`` scheme this fake validator was added by `PR 1377 `_. + +As a team, we do not consider it as an ideal solution. From one side, the community wants to have an option to work with self-signed certificates. +But from other side, currently source code scanners detect 2 serious security vulnerabilities because of this fake validator in `20.0 release `_. +The Ocelot team will rethink this unfortunate situation, and it is highly likely that this feature will at least be redesigned or removed completely. + +For now, the SSL fake validator makes sense in local development environments when a route has ``https`` or ``wss`` schemes having self-signed certificate for those routes. +There are no other reasons to use the **DangerousAcceptAnyServerCertificateValidator** property at all! + +As a team, we highly recommend following these instructions when developing your gateway app with Ocelot: + +* **Local development environments**. Use the feature to avoid SSL errors for self-signed certificates in case of ``https`` or ``wss`` schemes. + We understand that some routes should have dowstream scheme exactly with SSL, because they are also in development, and/or deployed using SSL protocols. + But we believe that especially for local development, you can switch from ``https`` to ``http`` without any objection since the services are in development and there is no risk of data leakage. + +* **Remote development environments**. Everything is the same as for local development. But this case is less strict, you have more options to use real certificates to switch off the feature. + For instance, you can deploy downstream services to cloud & hosting providers which have own signed certificates for SSL. + At least your team can deploy one remote web server to host downstream services. Install own certificate or use cloud provider's one. + +* **Staging or testing environments**. We do not recommend to use self-signed certificates because web servers should have valid certificates installed. + Ask your system administrator or DevOps engineers of your team to create valid certificates. + +* **Production environments**. **Do not use self-signed certificates at all!** + System administrators or DevOps engineers must create real valid certificates being signed by hosting or cloud providers. + **Switch off the feature for all routes!** Remove the **DangerousAcceptAnyServerCertificateValidator** property for all routes in production version of **ocelot.json** file! React to Configuration Changes ------------------------------ -Resolve ``IOcelotConfigurationChangeTokenSource`` from the DI container if you wish to react to changes to the Ocelot configuration via the Ocelot.Administration API -or **ocelot.json** being reloaded from the disk. You may either poll the change token's HasChanged property, or register a callback with the ``RegisterChangeCallback`` method. +Resolve ``IOcelotConfigurationChangeTokenSource`` interface from the DI container if you wish to react to changes to the Ocelot configuration via the :doc:`../features/administration` API or **ocelot.json** being reloaded from the disk. +You may either poll the change token's ``IChangeToken.HasChanged`` property, or register a callback with the ``RegisterChangeCallback`` method. Polling the HasChanged property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -252,11 +294,13 @@ Polling the HasChanged property { private readonly IOcelotConfigurationChangeTokenSource _tokenSource; private readonly ILogger _logger; + public ConfigurationNotifyingService(IOcelotConfigurationChangeTokenSource tokenSource, ILogger logger) { _tokenSource = tokenSource; _logger = logger; } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) @@ -279,6 +323,7 @@ Registering a callback { private readonly IOcelotConfigurationChangeTokenSource _tokenSource; private readonly IDisposable _callbackHolder; + public MyClass(IOcelotConfigurationChangeTokenSource tokenSource) { _tokenSource = tokenSource; @@ -293,8 +338,8 @@ Registering a callback DownstreamHttpVersion --------------------- -Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as "1.0", "1.1" or "2.0". - -"""" - -.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section. +Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as ``1.0``, ``1.1`` or ``2.0``. + +"""" + +.. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section. diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index aaf325b78..019b4dc21 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -1,64 +1,67 @@ Delegating Handlers =================== -Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ -and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 `_. +Ocelot allows the user to add delegating handlers to the ``HttpClient`` transport. +This feature was requested by `issue 208 `_ and the team decided that it was going to be useful in various ways. +Since then we extended it in `issue 264 `_. -Usage -^^^^^ +How to Use +---------- -In order to add delegating handlers to the HttpClient transport you need to do two main things. +In order to add delegating handlers to the ``HttpClient`` transport you need to do two main things. -First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the -asp.net core container so you can inject any other services you have registered into the constructor of your handler. +**First**, in order to create a class that can be used a delegating handler it must look as follows. +We are going to register these handlers in the ASP.NET Core DI container, so you can inject any other services you have registered into the constructor of your handler. .. code-block:: csharp public class FakeHandler : DelegatingHandler { - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken token) { - //do stuff and optionally call the base handler.. - return await base.SendAsync(request, cancellationToken); + // Do stuff and optionally call the base handler... + return await base.SendAsync(request, token); } } -Next you must add the handlers to Ocelot's container like below... +**Second**, you must add the handlers to DI container like below: .. code-block:: csharp - services.AddOcelot() - .AddDelegatingHandler() - .AddDelegatingHandler() - -Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of the DelegatingHandler is to be applied to specific Routes via ocelot.json (more on that later). If it is set to true -then it becomes a global handler and will be applied to all Routes. + ConfigureServices(s => s + .AddOcelot() + .AddDelegatingHandler() + .AddDelegatingHandler() + ) -e.g. - -As below... +Both of these ``AddDelegatingHandler`` methods have a default parameter called global which is set to ``false``. +If it is ``false`` then the intent of the *Delegating Handler* is to be applied to specific Routes via **ocelot.json** (more on that later). +If it is set to ``true`` then it becomes a global handler and will be applied to all Routes, as below: .. code-block:: csharp services.AddOcelot() - .AddDelegatingHandler(true) + .AddDelegatingHandler(true) -Finally if you want Route specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers then you must add the following json to the specific Route in ocelot.json. The names in the array must match the class names of your -DelegatingHandlers for Ocelot to match them together. +**Finally**, if you want Route specific *Delegating Handlers* or to order your specific and (or) global (more on this later) *Delegating Handlers* then you must add the following to the specific Route in **ocelot.json**. +The names in the array must match the class names of your *Delegating Handlers* for Ocelot to match them together: .. code-block:: json - "DelegatingHandlers": [ - "FakeHandlerTwo", - "FakeHandler" - ] + "DelegatingHandlers": [ + "FakeHandlerTwo", + "FakeHandler" + ] + +Order of Execution +------------------ -You can have as many DelegatingHandlers as you want and they are run in the following order: +You can have as many *Delegating Handlers* as you want and they are run in the following order: -1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. -2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. -3. Tracing DelegatingHandler if enabled (see tracing docs). -4. QoS DelegatingHandler if enabled (see QoS docs). -5. The HttpClient sends the HttpRequestMessage. +1. Any globals that are left in the order they were added to services and are not in the **DelegatingHandlers** array from **ocelot.json**. +2. Any non global *Delegating Handlers* plus any globals that were in the **DelegatingHandlers** array from **ocelot.json** ordered as they are in the **DelegatingHandlers** array. +3. Tracing *Delegating Handler*, if enabled (see :doc:`../features/tracing` docs). +4. Quality of Service *Delegating Handler*, if enabled (see :doc:`../features/qualityofservice` docs). +5. The ``HttpClient`` sends the ``HttpRequestMessage``. Hopefully other people will find this feature useful! diff --git a/docs/features/dependencyinjection.rst b/docs/features/dependencyinjection.rst index ceabb6cbb..711340efc 100644 --- a/docs/features/dependencyinjection.rst +++ b/docs/features/dependencyinjection.rst @@ -9,7 +9,7 @@ Overview Dependency Injection feature in Ocelot is designed to extend and/or control building of Ocelot core as ASP.NET MVC pipeline services. The main methods are `AddOcelot <#the-addocelot-method>`_ and `AddOcelotUsingBuilder <#the-addocelotusingbuilder-method>`_ of the ``ServiceCollectionExtensions`` class. -Use them in **Program.cs** and **Startup.cs** of your ASP.NET MVC gateway app (minimal web app) to enable and build the core of Ocelot. +Use them in **Program.cs** and **Startup.cs** of your ASP.NET MVC gateway app (minimal web app) to enable and build Ocelot pipeline. And of course, the `OcelotBuilder <#the-ocelotbuilder-class>`_ class is the core of Ocelot. @@ -18,33 +18,35 @@ IServiceCollection extensions **Class**: `Ocelot.DependencyInjection.ServiceCollectionExtensions `_ -Based on the current implementations for the ``OcelotBuilder`` class, the ``AddOcelot`` method adds default ASP.NET services to DI-container. +Based on the current implementations for the ``OcelotBuilder`` class, the ``AddOcelot`` method adds default ASP.NET services to DI container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. The AddOcelot method ^^^^^^^^^^^^^^^^^^^^ - | **Signatures**: - | ``IOcelotBuilder AddOcelot(this IServiceCollection services)`` - | ``IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration)`` +**Signatures**: + +* ``IOcelotBuilder AddOcelot(this IServiceCollection services)`` +* ``IOcelotBuilder AddOcelot(this IServiceCollection services, IConfiguration configuration)`` This ``IServiceCollection`` extension method adds default ASP.NET services and Ocelot application services with configuration injected implicitly or explicitly. -Note! The method adds default ASP.NET services required for Ocelot core in the `AddDefaultAspNetServices <#the-adddefaultaspnetservices-method>`_ method which plays the role of default builder. +Note! The method adds **default** ASP.NET services required for Ocelot core in the `AddDefaultAspNetServices <#the-adddefaultaspnetservices-method>`_ method which plays the role of default builder. In this scenario, you do nothing except calling the ``AddOcelot`` method which has been mentioned in feature chapters, if additional startup settings are required. -In this case you just reuse default settings to build Ocelot core. The alternative is ``AddOcelotUsingBuilder`` method, see the next paragraph. +In this case you just reuse default settings to build Ocelot core. The alternative is ``AddOcelotUsingBuilder`` method, see the next section. The AddOcelotUsingBuilder method ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | **Signatures**: - | ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func customBuilder)`` - | ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func customBuilder)`` +**Signatures**: + +* ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func customBuilder)`` +* ``IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, IConfiguration configuration, Func customBuilder)`` This ``IServiceCollection`` extension method adds Ocelot application services, and it *adds custom ASP.NET services* with configuration injected implicitly or explicitly. -Note! The method adds **custom** ASP.NET services required for Ocelot core using custom builder (``customBuilder`` parameter). +Note! The method adds **custom** ASP.NET services required for Ocelot pipeline using custom builder (``customBuilder`` parameter). It is highly recommended to read docs of the `AddDefaultAspNetServices <#the-adddefaultaspnetservices-method>`_ method, -or even to review implementation to understand default ASP.NET services which are the minimal part of the gateway core. +or even to review implementation to understand default ASP.NET services which are the minimal part of the gateway pipeline. In this custom scenario, you control everything during ASP.NET MVC pipeline building, and you provide custom settings to build Ocelot core. @@ -56,26 +58,28 @@ The OcelotBuilder class The ``OcelotBuilder`` class is the core of Ocelot which does the following: - Contructs itself by single public constructor: - ``public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot, Func customBuilder = null)`` -- Initializes and stores public properties: - **Services** (``IServiceCollection`` object), **Configuration** (``IConfiguration`` object) and **MvcCoreBuilder** (``IMvcCoreBuilder`` object) + ``public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot, Func customBuilder = null)`` +- Initializes and stores public properties: **Services** (``IServiceCollection`` object), **Configuration** (``IConfiguration`` object) and **MvcCoreBuilder** (``IMvcCoreBuilder`` object) - Adds **all application services** during construction phase over the ``Services`` property - Adds ASP.NET services by builder using ``Func`` object in these 2 development scenarios: - - by default builder (``AddDefaultAspNetServices`` method) if there is no ``customBuilder`` parameter provided - - by custom builder with provided delegate object as ``customBuilder`` parameter + + * by default builder (``AddDefaultAspNetServices`` method) if there is no ``customBuilder`` parameter provided + * by custom builder with provided delegate object as the ``customBuilder`` parameter + - Adds (switches on/off) Ocelot features by: - - ``AddSingletonDefinedAggregator`` and ``AddTransientDefinedAggregator`` methods - - ``AddCustomLoadBalancer`` method - - ``AddDelegatingHandler`` method - - ``AddConfigPlaceholders`` method + + * ``AddSingletonDefinedAggregator`` and ``AddTransientDefinedAggregator`` methods + * ``AddCustomLoadBalancer`` method + * ``AddDelegatingHandler`` method + * ``AddConfigPlaceholders`` method The AddDefaultAspNetServices method ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Class**: `Ocelot.DependencyInjection.OcelotBuilder `_ -Currently the method is protected and overriding is forbidden. The role of the method is to inject required services via both ``IServiceCollection`` and ``IMvcCoreBuilder`` interfaces objects -for the minimal part of the gateway core. +Currently the method is protected and overriding is forbidden. +The role of the method is to inject required services via both ``IServiceCollection`` and ``IMvcCoreBuilder`` interfaces objects for the minimal part of the gateway pipeline. Current implementation is the folowing: @@ -96,24 +100,25 @@ Current implementation is the folowing: } The method cannot be overridden. It is not virtual, and there is no way to override current behavior by inheritance. -And, the method is default builder of Ocelot core while calling the `AddOcelot <#the-addocelot-method>`_ method. +And, the method is default builder of Ocelot pipeline while calling the `AddOcelot <#the-addocelot-method>`_ method. As alternative, to "override" this default builder, you can design and reuse custom builder as a ``Func`` delegate object and pass it as parameter to the `AddOcelotUsingBuilder <#the-addocelotusingbuilder-method>`_ extension method. -It gives you full control on design and buiding of Ocelot core, but be careful while designing your custom Ocelot core as customizable ASP.NET MVC pipeline. +It gives you full control on design and buiding of Ocelot pipeline, but be careful while designing your custom Ocelot pipeline as customizable ASP.NET MVC pipeline. -Warning! Most of services from minimal part of the core should be reused, but only a few of services could be removed. +Warning! Most of services from minimal part of the pipeline should be reused, but only a few of services could be removed. Warning!! The method above is called after adding required services of ASP.NET MVC pipeline building by `AddMvcCore `_ method -over the ``Services`` property in upper calling context. These services are absolute minimum core services for ASP.NET MVC pipeline. They must be added to DI-container always, -and they are added implicitly before calling of the method by caller in upper context. So, ``AddMvcCore`` creates an ``IMvcCoreBuilder`` object with its assignment to the ``MvcCoreBuilder`` property. -Finally, as default builder the method above receives ``IMvcCoreBuilder`` object being ready for further extensions. +over the ``Services`` property in upper calling context. These services are absolute minimum core services for ASP.NET MVC pipeline. +They must be added to DI container always, and they are added implicitly before calling of the method by caller in upper context. +So, ``AddMvcCore`` creates an ``IMvcCoreBuilder`` object with its assignment to the ``MvcCoreBuilder`` property. +Finally, as a default builder, the method above receives ``IMvcCoreBuilder`` object being ready for further extensions. -The next paragraph shows you an example of designing custom Ocelot core by custom builder. +The next section shows you an example of designing custom Ocelot pipeline by custom builder. Custom Builder -------------- -**Goal**: Replace ``Newtonsoft.Json`` services by ``System.Text.Json`` services. +**Goal**: Replace ``Newtonsoft.Json`` services with ``System.Text.Json`` services. The Problem ^^^^^^^^^^^ @@ -139,7 +144,7 @@ We have the following methods in ``Ocelot.DependencyInjection.ServiceCollectionE These method with custom builder allows you to use your any desired JSON library for (de)serialization. But we are going to create custom ``MvcCoreBuilder`` with support of JSON services, such as ``System.Text.Json``. To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreBuilderExtensions`` class -(NuGet package: `Microsoft.AspNetCore.Mvc.Core `_) in **Startup.cs** file: +(NuGet package: `Microsoft.AspNetCore.Mvc.Core `_) in **Startup.cs**: .. code-block:: csharp @@ -155,7 +160,7 @@ To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreB .AddLogging() .AddMiddlewareAnalysis() .AddWebEncoders() - + // Add your custom builder .AddOcelotUsingBuilder(MyCustomBuilder); } @@ -174,5 +179,5 @@ To do that we need to call ``AddJsonOptions`` extension of the ``MvcCoreMvcCoreB } } -This sample code provides settings to render JSON as indented text rather than zipped plain JSON text. -And, this is just one of the common usages, you can add more services you need in the builder. +The sample code provides settings to render JSON as indented text rather than compressed plain JSON text without spaces. +This is just one common use case, and you can add additional services to the builder. diff --git a/docs/features/errorcodes.rst b/docs/features/errorcodes.rst index a9fffd2a6..f99076744 100644 --- a/docs/features/errorcodes.rst +++ b/docs/features/errorcodes.rst @@ -1,5 +1,5 @@ -HTTP Error Status Codes -======================= +Error Status Codes +================== Ocelot will return HTTP status error codes based on internal logic in certain situations: @@ -17,3 +17,20 @@ Server error responses - **500** - if unable to complete the HTTP request to downstream service, and the exception is not ``OperationCanceledException`` or ``HttpRequestException``. - **502** - if unable to connect to downstream service. - **503** - if the downstream request times out. + +Design +------ + +Historically Ocelot errors are implemented by the `HttpExceptionToErrorMapper `_ class. +The ``Map`` method converts a ``System.Exception`` object to native ``Ocelot.Errors.Error`` object. + +We do HTTP status code overriding because of Exception-to-Error mapping. +This can be confusing for the developer since the actual status code of the downstream service may be different and get lost. +Please, research and review all response headers of upstream service. +If you did not find statuses and (or) required headers then :doc:`../features/headerstransformation` feature should help. + +.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png + :alt: octocat + :width: 23 + +We expect you to share your user case with us in the `Discussions `_ space of the repository. |octocat| diff --git a/docs/features/graphql.rst b/docs/features/graphql.rst index 5c74c0564..93007f0ba 100644 --- a/docs/features/graphql.rst +++ b/docs/features/graphql.rst @@ -1,12 +1,29 @@ -GraphQL -======= +.. |GraphQL Logo| image:: https://avatars.githubusercontent.com/u/13958777 + :alt: GraphQL Logo + :width: 40 -OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate the `graphql-dotnet `_ library. +|GraphQL Logo| GraphQL +====================== +Ocelot doesn't directly support `GraphQL `_, but so many people have asked about it. +We wanted to show how easy it is to integrate the `GraphQL for .NET `_ library. -Please see the sample project `OcelotGraphQL `_. Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do. -However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give you enough instruction on how to do this! -Good luck and have fun :> +Please see the sample project `OcelotGraphQL `_. +Using a combination of the `graphql-dotnet `_ project and Ocelot :doc:`../features/delegatinghandlers` features, this is pretty easy to do. +However we do not intend to integrate more closely with **GraphQL** at the moment. +Check out the samples `README.md `_ and that should give you enough instruction on how to do this! +Future +------ +If you have sufficient experience with GraphQL and mentioned .NET `package `_, we would welcome your contribution to the sample. |octocat| + +.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png + :alt: octocat + :width: 23 + +Who knows, maybe you'll get inspired by the sample development and come up with some design solution in the form of a rough draft of GraphQL feature to implement in Ocelot. +Good luck! + +And, welcome to `Discussions `_ space of the repository! diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 0250a0fd8..c400989c1 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -1,150 +1,177 @@ Headers Transformation ====================== -Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. +Ocelot allows the user to transform headers pre and post downstream request. +At the moment Ocelot only supports find and replace. +This feature was requested in `issue 190 `_ and the team decided that it was going to be useful in various ways. Add to Request -^^^^^^^^^^^^^^ +-------------- -This feature was requested in `GitHub #313 `_. +This feature was requested in `issue 313 `_. -If you want to add a header to your upstream request please add the following to a Route in your ocelot.json: +If you want to add a header to your upstream request please add the following to a Route in your **ocelot.json**: .. code-block:: json - "UpstreamHeaderTransform": { - "Uncle": "Bob" - } + "UpstreamHeaderTransform": { + "Uncle": "Bob" + } -In the example above a header with the key Uncle and value Bob would be send to to the upstream service. +In the example above a header with the key ``Uncle`` and value ``Bob`` would be send to to the upstream service. Placeholders are supported too (see below). Add to Response -^^^^^^^^^^^^^^^ +--------------- -This feature was requested in `GitHub #280 `_. +This feature was requested in `issue 280 `_. -If you want to add a header to your downstream response please add the following to a Route in ocelot.json.. +If you want to add a header to your downstream response, please add the following to a Route in **ocelot.json**: .. code-block:: json - "DownstreamHeaderTransform": { - "Uncle": "Bob" - }, + "DownstreamHeaderTransform": { + "Uncle": "Bob" + } -In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific Route. +In the example above a header with the key ``Uncle`` and value ``Bob`` would be returned by Ocelot when requesting the specific Route. -If you want to return the Butterfly APM trace id then do something like the following.. +If you want to return the `Butterfly APM `_ trace id then do something like the following: .. code-block:: json - "DownstreamHeaderTransform": { - "AnyKey": "{TraceId}" - }, + "DownstreamHeaderTransform": { + "AnyKey": "{TraceId}" + } Find and Replace -^^^^^^^^^^^^^^^^ +---------------- In order to transform a header first we specify the header key and then the type of transform we want e.g. .. code-block:: json - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" -The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more. +The key is ``Test`` and the value is ``http://www.bbc.co.uk/, http://ocelot.com/``. +The value is saying: replace ``http://www.bbc.co.uk/`` with ``http://ocelot.com/``. +The syntax is ``{find}, {replace}``. Hopefully pretty simple. There are examples below that explain more. Pre Downstream Request -^^^^^^^^^^^^^^^^^^^^^^ +---------------------- -Add the following to a Route in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. +Add the following to a Route in **ocelot.json** in order to replace ``http://www.bbc.co.uk/`` with ``http://ocelot.com/``. +This header will be changed before the request downstream and will be sent to the downstream server. .. code-block:: json - "UpstreamHeaderTransform": { - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - }, + "UpstreamHeaderTransform": { + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + } Post Downstream Request -^^^^^^^^^^^^^^^^^^^^^^^ +----------------------- -Add the following to a Route in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. +Add the following to a Route in **ocelot.json** in order to replace ``http://www.bbc.co.uk/`` with ``http://ocelot.com/``. +This transformation will take place after Ocelot has received the response from the downstream service. .. code-block:: json - "DownstreamHeaderTransform": { - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - }, + "DownstreamHeaderTransform": { + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + } Placeholders -^^^^^^^^^^^^ +------------ Ocelot allows placeholders that can be used in header transformation. -{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP. -{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. -{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. -{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. -{UpstreamHost} - This will look for the incoming Host header. +* ``{BaseUrl}`` - This will use Ocelot base URL e.g. ``http://localhost:5000`` as its value. +* ``{DownstreamBaseUrl}`` - This will use the downstream services base URL e.g. ``http://localhost:5000`` as its value. This only works for **DownstreamHeaderTransform** at the moment. +* ``{RemoteIpAddress}`` - This will find the clients IP address using ``IHttpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString()``, so you will get back some IP. See more in the `GetRemoteIpAddress `_ method. +* ``{TraceId}`` - This will use the `Butterfly APM `_ Trace Id. This only works for **DownstreamHeaderTransform** at the moment. +* ``{UpstreamHost}`` - This will look for the incoming Host header. + +For now, we believe these placeholders are sufficient for basic user scenarios. +But if you need more placeholders, you can head to the `future <#future>`_. Handling 302 Redirects -^^^^^^^^^^^^^^^^^^^^^^ -Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration. +---------------------- + +Ocelot will by default automatically follow redirects, however if you want to return the location header to the client, you might want to change the location to be Ocelot not the downstream service. +Ocelot allows this with the following configuration: .. code-block:: json - "DownstreamHeaderTransform": { - "Location": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, + "DownstreamHeaderTransform": { + "Location": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + } -or you could use the BaseUrl placeholder. +Or, you could use the **BaseUrl** placeholder. .. code-block:: json - "DownstreamHeaderTransform": { - "Location": "http://localhost:6773, {BaseUrl}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, + "DownstreamHeaderTransform": { + "Location": "http://localhost:6773, {BaseUrl}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + } -finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. +Finally, if you are using a load balancer with Ocelot, you will get multiple downstream base URLs so the above would not work. +In this case you can do the following: .. code-block:: json - "DownstreamHeaderTransform": { - "Location": "{DownstreamBaseUrl}, {BaseUrl}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, + "DownstreamHeaderTransform": { + "Location": "{DownstreamBaseUrl}, {BaseUrl}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + } X-Forwarded-For -^^^^^^^^^^^^^^^ +--------------- -An example of using {RemoteIpAddress} placeholder... +An example of using ``{RemoteIpAddress}`` placeholder: .. code-block:: json "UpstreamHeaderTransform": { - "X-Forwarded-For": "{RemoteIpAddress}" - } + "X-Forwarded-For": "{RemoteIpAddress}" + } Future -^^^^^^ +------ -Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. It would also be nice if it could multi find and replace e.g. +Ideally this feature would be able to support the fact that a header can have multiple values. +At the moment it just assumes one. +It would also be nice if it could multi find and replace e.g. .. code-block:: json - "DownstreamHeaderTransform": { - "Location": "[{one,one},{two,two}]" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, + "DownstreamHeaderTransform": { + "Location": "[{one,one},{two,two}]" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + } + +If anyone wants to have a go at this please, help yourself! + +Global Headers Transformation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We have pending open `PR 1659 `_ for the `1658 `_ issue. +Current `20.0 `_ version provides Route-level *Headers Transformation* feature, +but we hope **global** transformations will be included in the next upcoming `release `_. + +.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png + :alt: octocat + :width: 23 -If anyone wants to have a go at this please help yourself! +Any ideas and proposals can be shared in the `Discussions `_ space of the repository! |octocat| diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst index 3a4bfd861..a2e17fd08 100644 --- a/docs/features/kubernetes.rst +++ b/docs/features/kubernetes.rst @@ -1,24 +1,31 @@ -Kubernetes -============== +.. |K8s Logo| image:: https://kubernetes.io/images/favicon.png + :alt: K8s Logo + :width: 40 -This feature was requested as part of `Issue 345 `_ . to add support for kubernetes's provider. +|K8s Logo| Kubernetes +===================== -Ocelot will call the k8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them. Ocelot used to use the services api to send requests to the k8s service but this was changed in `PR 1134 `_ because the service did not load balance as expected. + Feature: :doc:`../features/servicediscovery` -The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot. +This feature was requested as part of `issue 345 `_ to add support for `Kubernetes `_ service discovery provider. + +Ocelot will call the K8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them. +Ocelot used to use the services API to send requests to the K8s service but this was changed in `PR 1134 `_ because the service did not load balance as expected. + +The first thing you need to do is install the `NuGet package `_ that provides Kubernetes support in Ocelot: .. code-block:: powershell Install-Package Ocelot.Provider.Kubernetes -Then add the following to your ConfigureServices method. +Then add the following to your ConfigureServices method: .. code-block:: csharp - s.AddOcelot().AddKubernetes(); + services.AddOcelot().AddKubernetes(); -If you have services deployed in kubernetes you will normally use the naming service to access them. -Default ``usePodServiceAccount = true``, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization: +If you have services deployed in Kubernetes, you will normally use the naming service to access them. +Default ``usePodServiceAccount = true``, which means that Service Account using Pod to access the service of the K8s cluster needs to be Service Account based on RBAC authorization: .. code-block:: csharp @@ -27,72 +34,72 @@ Default ``usePodServiceAccount = true``, which means that ServiceAccount using P public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true); } -You can replicate a Permissive. Using RBAC role bindings. -`Permissive RBAC Permissions `_, k8s api server and token will read from pod. +You can replicate a Permissive using RBAC role bindings (see `Permissive RBAC Permissions `_), +K8s API server and token will read from pod. .. code-block:: bash - kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts + kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts -The following example shows how to set up a Route that will work in kubernetes. The most important thing is the ServiceName which is made up of the kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration: +The following example shows how to set up a Route that will work in Kubernetes. +The most important thing is the **ServiceName** which is made up of the Kubernetes service name. +We also need to set up the **ServiceDiscoveryProvider** in **GlobalConfiguration**. +The example here shows a typical configuration: .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/values", - "ServiceName": "downstreamservice", - "UpstreamHttpMethod": [ "Get" ] - } - ], - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "kube" - } + { + "Routes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/values", + "ServiceName": "downstreamservice", + "UpstreamHttpMethod": [ "Get" ] + } + ], + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "kube" } } + } -Service deployment in Namespace Dev, ServiceDiscoveryProvider type is kube, you also can set "pollkube" ServiceDiscoveryProvider type. -Note: Host, Port and Token are no longer in use. +Service deployment in **Namespace** ``dev``, **ServiceDiscoveryProvider** type is ``kube``, you also can set ``pollkube`` **ServiceDiscoveryProvider** type. +Note: **Host**, **Port** and **Token** are no longer in use. -You use Ocelot to poll kubernetes for latest service information rather than per request. -If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration: +You use Ocelot to poll Kubernetes for latest service information rather than per request. +If you want to poll Kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration: .. code-block:: json "ServiceDiscoveryProvider": { - "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + // ... "Namespace": "dev", "Type": "pollkube", "PollingInterval": 100 } -The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. +The polling interval is in milliseconds and tells Ocelot how often to call Kubernetes for changes in service configuration. -Please note there are tradeoffs here. If you poll kubernetes it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. There is no way for Ocelot to work these out for you. +Please note, there are tradeoffs here. +If you poll Kubernetes, it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. +This really depends on how volatile your services are. +We doubt it will matter for most people and polling may give a tiny performance improvement over calling Kubernetes per request. +There is no way for Ocelot to work these out for you. -If your downstream service resides in a different namespace you can override the global setting at the Route level by specifying a ServiceNamespace: +If your downstream service resides in a different namespace, you can override the global setting at the Route-level by specifying a **ServiceNamespace**: .. code-block:: json + "Routes": [ { - "Routes": [ - { - "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/values", - "ServiceName": "downstreamservice", - "ServiceNamespace": "downstream-namespace", - "UpstreamHttpMethod": [ "Get" ] - } - ] + // ... + "ServiceName": "downstreamservice", + "ServiceNamespace": "downstream-namespace" } + ] diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 6ddc9984c..1fe54bdba 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -1,204 +1,196 @@ Load Balancer ============= -Ocelot can load balance across available downstream services for each Route. This means you can scale your downstream services and Ocelot can use them effectively. +Ocelot can load balance across available downstream services for each Route. +This means you can scale your downstream services and Ocelot can use them effectively. -The type of load balancer available are: +The types of load balancer available are: - LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. - - RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. - - NoLoadBalancer - takes the first available service from config or service discovery. - - CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. +* **LeastConnection** tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorithm state is not distributed across a cluster of Ocelot's. +* **RoundRobin** loops through available services and sends requests. The algorithm state is not distributed across a cluster of Ocelot's. +* **NoLoadBalancer** takes the first available service from config or service discovery. +* **CookieStickySessions** uses a cookie to stick all requests to a specific server. More info below. You must choose in your configuration which load balancer to use. Configuration -^^^^^^^^^^^^^ +------------- -The following shows how to set up multiple downstream services for a Route using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. +The following shows how to set up multiple downstream services for a Route using **ocelot.json** and then select the ``LeastConnection`` load balancer. +This is the simplest way to get load balancing set up. .. code-block:: json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] + { + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put", "Delete" ], + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "10.0.1.10", "Port": 5000 }, + { "Host": "10.0.1.11", "Port": 5000 } + ], + "LoadBalancerOptions": { + "Type": "LeastConnection" } - + } Service Discovery -^^^^^^^^^^^^^^^^^ +----------------- -The following shows how to set up a Route using service discovery then select the LeastConnection load balancer. +The following shows how to set up a Route using service discovery then select the ``LeastConnection`` load balancer. .. code-block:: json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, + { + // ... + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" } + } -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the -service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. +If you add and remove services from the service discovery provider (Consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. -CookieStickySessions -^^^^^^^^^^^^^^^^^^^^ +CookieStickySessions Type +------------------------- -I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! +We have implemented a really basic sticky session type of load balancer. +The scenario it is meant to support is you have a bunch of downstream servers that don't share session state, so if you get more than one request for one of these servers then it should go to the same box each time or the session state might be incorrect for the given user. +This feature was requested in `issue 322 `_ though what the user wants is more complicated than just sticky sessions. +Anyway, we thought this would be a nice feature to have! -In order to set up CookieStickySessions load balancer you need to do something like the following. +In order to set up **CookieStickySessions** load balancer you need to do something like the following: .. code-block:: json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "CookieStickySessions", - "Key": "ASP.NET_SessionId", - "Expiry": 1800000 - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] + { + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put", "Delete" ], + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "10.0.1.10", "Port": 5000 }, + { "Host": "10.0.1.11", "Port": 5000 } + ], + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 } + } + +The **LoadBalancerOptions** are -The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this refreshes on every request which is meant to mimick how sessions work usually. +* **Type** this needs to be ``CookieStickySessions`` +* **Key** this is the key of the cookie you wish to use for the sticky sessions +* **Expiry** this is how long in milliseconds you want to the session to be stuck for. Remember this refreshes on every request which is meant to mimick how sessions work usually. -If you have multiple Routes with the same LoadBalancerOptions then all of those Routes will use the same load balancer for there subsequent requests. This means the sessions will be stuck across Routes. +If you have multiple Routes with the same **LoadBalancerOptions** then all of those Routes will use the same load balancer for there subsequent requests. +This means the sessions will be stuck across Routes. -Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the moment but could be changed. +Please note that if you give more than one **DownstreamHostAndPort** or you are using a Service Discovery provider such as Consul and this returns more than one service then **CookieStickySessions** uses round robin to select the next server. +This is hard coded at the moment but could be changed. Custom Load Balancers -^^^^^^^^^^^^^^^^^^^^^ +--------------------- -`David Lievrouw `_ implemented a way to provide Ocelot with custom load balancer in `PR 1155 `_. +`David Lievrouw `_ implemented a way to provide Ocelot with custom load balancer in `PR 1155 `_ +(his `issue 961 `_). -In order to create and use a custom load balancer you can do the following. Below we setup a basic load balancing config and not the Type is CustomLoadBalancer this is the name of a class we will setup to do load balancing. +In order to create and use a custom load balancer you can do the following. +Below we setup a basic load balancing config and not the **Type** is ``CustomLoadBalancer`` which is the name of a class we will setup to do load balancing. .. code-block:: json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "CustomLoadBalancer" - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] + { + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put", "Delete" ], + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "10.0.1.10", "Port": 5000 }, + { "Host": "10.0.1.11", "Port": 5000 } + ], + "LoadBalancerOptions": { + "Type": "CustomLoadBalancer" } + } - -Then you need to create a class that implements the ILoadBalancer interface. Below is a simple round robin example. +Then you need to create a class that implements the ``ILoadBalancer`` interface. Below is a simple round robin example: .. code-block:: csharp - public class CustomLoadBalancer : ILoadBalancer + public class CustomLoadBalancer : ILoadBalancer + { + private readonly Func>> _services; + private readonly object _lock = new object(); + private int _last; + + public CustomLoadBalancer(Func>> services) { - private readonly Func>> _services; - private readonly object _lock = new object(); - private int _last; - - public CustomLoadBalancer(Func>> services) - { - _services = services; - } - - public async Task> Lease(HttpContext httpContext) + _services = services; + } + + public async Task> Lease(HttpContext httpContext) + { + var services = await _services?.Invoke(); + lock (_lock) { - var services = await _services(); - lock (_lock) - { - if (_last >= services.Count) - _last = 0; - - var next = services[_last++]; - return new OkResponse(next.HostAndPort); - } + if (_last >= services.Count) + _last = 0; + + var next = services[_last++]; + return new OkResponse(next.HostAndPort); } - - public void Release(ServiceHostAndPort hostAndPort) { } } + + public void Release(ServiceHostAndPort hostAndPort) { } + } -Finally you need to register this class with Ocelot. I have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers. - -.. code-block:: csharp - - Func loadBalancerFactoryFunc = (serviceProvider, Route, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get); - - s.AddOcelot() - .AddCustomLoadBalancer(loadBalancerFactoryFunc); +Finally, you need to register this class with Ocelot. -However there is a much simpler example that will work the same. +We have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers. .. code-block:: csharp - s.AddOcelot() - .AddCustomLoadBalancer(); + Func loadBalancerFactoryFunc = + (serviceProvider, Route, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get); + + services.AddOcelot() + .AddCustomLoadBalancer(loadBalancerFactoryFunc); -There are numerous extension methods to add a custom load balancer and the interface is as follows. +However, there is a much simpler example that will work the same: .. code-block:: csharp - IOcelotBuilder AddCustomLoadBalancer() - where T : ILoadBalancer, new(); - - IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) - where T : ILoadBalancer; + services.AddOcelot() + .AddCustomLoadBalancer(); - IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) - where T : ILoadBalancer; +There are numerous extension methods to add a custom load balancer and the interface is as follows: - IOcelotBuilder AddCustomLoadBalancer( - Func loadBalancerFactoryFunc) - where T : ILoadBalancer; +.. code-block:: csharp - IOcelotBuilder AddCustomLoadBalancer( - Func loadBalancerFactoryFunc) - where T : ILoadBalancer; + IOcelotBuilder AddCustomLoadBalancer() + where T : ILoadBalancer, new(); + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; -When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. If it finds a match it will use your load balaner to load balance. If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class then you will receive a HTTP 500 internal server error. If your load balancer factory throw an exception when Ocelot calls it you will receive a HTTP 500 internal server error. +When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. +If it finds a match, it will use your load balaner to load balance. +If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class +then you will receive a HTTP `500 Internal Server Error `_. +If your load balancer factory throw an exception when Ocelot calls it, you will receive a HTTP `500 Internal Server Error `_. -Remember if you specify no load balancer in your config Ocelot will not try and load balance. +Remember, if you specify no load balancer in your config, Ocelot will not try and load balance. diff --git a/docs/features/logging.rst b/docs/features/logging.rst index f92a04377..36572f778 100644 --- a/docs/features/logging.rst +++ b/docs/features/logging.rst @@ -2,21 +2,23 @@ Logging ======= Ocelot uses the standard logging interfaces ``ILoggerFactory`` and ``ILogger`` at the moment. -This is encapsulated in ``IOcelotLogger`` and ``IOcelotLoggerFactory`` with an implementation for the standard ASP.NET Core logging stuff at the moment. +This is encapsulated in ``IOcelotLogger`` and ``IOcelotLoggerFactory`` with an implementation for the standard `ASP.NET Core logging `_ stuff at the moment. This is because Ocelot adds some extra info to the logs such as **request ID** if it is configured. -There is a global error handler that should catch any exceptions thrown and log them as errors. +There is a global `error handler middleware `_ that should catch any exceptions thrown and log them as errors. -Finally, if logging is set to trace level, Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful. +Finally, if logging is set to **Trace** level, Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful. The reason for not just using `bog standard `_ framework logging is that -I could not work out how to override the request id that get's logged when setting **IncludeScopes** to ``true`` for logging settings. +we could not work out how to override the request id that get's logged when setting **IncludeScopes** to ``true`` for logging settings. Nicely onto the next feature. Warning ------- -If you are logging to `Console `_ you will get terrible performance. -I have had so many issues about performance issues with Ocelot and it is always logging level Debug, logging to `Console `_. +If you are logging to `Console `_, you will get terrible performance. +The team has had so many issues about performance issues with Ocelot and it is always logging level **Debug**, logging to `Console `_. * **Warning!** Make sure you are logging to something proper in production environment! +* Use **Error** and **Critical** levels in production environment! +* Use **Warning** level in testing environment! diff --git a/docs/features/methodtransformation.rst b/docs/features/methodtransformation.rst index 0748e6fd2..30fb9c6af 100644 --- a/docs/features/methodtransformation.rst +++ b/docs/features/methodtransformation.rst @@ -1,5 +1,5 @@ -HTTP Method Transformation -========================== +Method Transformation +===================== Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service. @@ -7,22 +7,17 @@ This achieved by setting the following Route configuration: .. code-block:: json - { - "DownstreamPathTemplate": "/{url}", - "UpstreamPathTemplate": "/{url}", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamHttpMethod": "POST", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 54321 - } - ], - } + { + "UpstreamPathTemplate": "/{url}", + "DownstreamPathTemplate": "/{url}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 54321 } + ], + "UpstreamHttpMethod": [ "Get" ], + "DownstreamHttpMethod": "POST" // ! + } -The key property here is **DownstreamHttpMethod** which is set as POST and the Route will only match on GET as set by **UpstreamHttpMethod**. +The key property here is **DownstreamHttpMethod** which is set as ``POST`` and the Route will only match on ``GET`` as set by **UpstreamHttpMethod**. This feature can be useful when interacting with downstream APIs that only support POST and you want to present some kind of RESTful interface. diff --git a/docs/features/middlewareinjection.rst b/docs/features/middlewareinjection.rst index 811cc4ce2..bf0ac2152 100644 --- a/docs/features/middlewareinjection.rst +++ b/docs/features/middlewareinjection.rst @@ -1,58 +1,56 @@ -Middleware Injection and Overrides -================================== +Middleware Injection +==================== -Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware +Warning, use with caution! If you are seeing any exceptions or strange behavior in your middleware pipeline and you are using any of the following. Remove them and try again! -When setting up Ocelot in your Startup.cs you can provide some additional middleware -and override middleware. This is done as follows. +When setting up Ocelot in your **Startup.cs** you can provide some additional middleware and override middleware. +This is done as follows: .. code-block:: csharp var configuration = new OcelotPipelineConfiguration { - PreErrorResponderMiddleware = async (ctx, next) => + PreErrorResponderMiddleware = async (context, next) => { await next.Invoke(); } }; - app.UseOcelot(configuration); In the example above the provided function will run before the first piece of Ocelot middleware. This allows a user to supply any behaviors they want before and after the Ocelot pipeline has run. -This means you can break everything so use at your own pleasure! - -The user can set functions against the following. - -* PreErrorResponderMiddleware - Already explained above. - -* PreAuthenticationMiddleware - This allows the user to run pre authentication logic and then call Ocelot's authentication middleware. - -* AuthenticationMiddleware - This overrides Ocelots authentication middleware. - -* PreAuthorizationMiddleware - This allows the user to run pre authorization logic and then call Ocelot's authorization middleware. +This means you can break everything, so use at your own pleasure! -* AuthorizationMiddleware - This overrides Ocelots authorization middleware. +The user can set functions against the following (see more in the `OcelotPipelineConfiguration `_ class): -* PreQueryStringBuilderMiddleware - This allows the user to manipulate the query string on the http request before it is passed to Ocelots request creator. +* ``PreErrorResponderMiddleware`` injection is already explained above. +* ``PreAuthenticationMiddleware`` injection allows the user to run pre authentication logic and then call Ocelot authentication middleware. +* ``AuthenticationMiddleware`` overrides Ocelot authentication middleware. [#f1]_ +* ``PreAuthorizationMiddleware`` injection allows the user to run pre authorization logic and then call Ocelot authorization middleware. +* ``AuthorizationMiddleware`` overrides Ocelots authorization middleware. [#f1]_ +* ``PreQueryStringBuilderMiddleware`` injection allows the user to manipulate the query string on the http request before it is passed to Ocelot request creator. -Obviously you can just add mentioned Ocelot's middleware overridings as normal before the call to ``app.UseOcelot()``. -It cannot be added after as Ocelot does not call the next Ocelot's middleware overridings based on specified middleware configuration. -So, the next called middlewares won't affect Ocelot configuration. +Obviously you can just add mentioned Ocelot middleware overridings as normal before the call to ``app.UseOcelot()``. +It cannot be added after as Ocelot does not call the next Ocelot middleware overridings based on specified middleware configuration. +So, the next called middlewares **will not** affect Ocelot configuration. ASP.NET Core Middlewares and Ocelot Pipeline Builder ---------------------------------------------------- Ocelot pipeline is a part of entire `ASP.NET Core Middlewares `_ conveyor aka app pipeline. The `BuildOcelotPipeline `_ method encapsulates Ocelot pipeline. -The last middleware in the **BuildOcelotPipeline** method is **HttpRequesterMiddleware** that calls the next middleware. +The last middleware in the ``BuildOcelotPipeline`` method is ``HttpRequesterMiddleware`` that calls the next middleware, if added to the pipeline. -The internal `HttpRequesterMiddleware `_ is part of the pipeline but it is private and cannot be overridden, since this middleware does not belong to the list of user's one that can be overridden! -So, this is `the last middleware `_ of the entire Ocelot and ASP.NET pipeline, and it handles non-user operation. -The last user middleware that can be overridden is `PreQueryStringBuilderMiddleware `_ being read from the `pipeline configuration object `_, see previous section. +The internal `HttpRequesterMiddleware `_ is part of the pipeline +but it is private and cannot be overridden, since this middleware does not belong to the list of `user's public ones `_ that can be overridden! +So, this is `the last middleware `_ of the entire Ocelot +and ASP.NET `pipeline `_, and it handles non-user operation. +The last user (public) middleware that can be overridden is `PreQueryStringBuilderMiddleware `_ being +read from the `pipeline configuration object `_, +see `previous section <#middleware-injection-and-overrides>`_. -Considering that **PreQueryStringBuilderMiddleware** and **HttpRequesterMiddleware** are the last user and system middlewares, there are no other middlewares in the pipeline at all. +Considering that ``PreQueryStringBuilderMiddleware`` and ``HttpRequesterMiddleware`` are the last user and system middlewares, there are no other middlewares in the pipeline at all. But you can still extend the ASP.NET pipeline, as shown in the following code: .. code-block:: csharp @@ -63,6 +61,23 @@ But you can still extend the ASP.NET pipeline, as shown in the following code: But we do not recommend adding this custom middleware before or after calling of ``UseOcelot()`` as it affects the stability of the entire pipeline and has not been tested. Such kind of custom pipeline building is out of the Ocelot pipeline model and the quality of the solution is at your own risk. -Finally, don't get confused about the distinction between system (private) and user (public) middlewares. +Finally, do not get confused about the distinction between system (private, non-overridden) and user (public, overridden) middlewares. Private middlewares are hidden and cannot be overridden, but the entire ASP.NET pipeline can still be extended. -The public middlewares are fully customizable and can be overridden. +The `public middlewares `_ are fully customizable and can be overridden. + +Future +------ + +The community shows an interest in adding more overriden middlewares. +One of such request is `PR 1497 `_, and possibly it will be included in a next upcoming `release `_. + +Anyway, in your opinion, if current overriden middlewares do not provide enough pipeline flexibility, +you can open new topic in `Discussions `_ space of the repository. |octocat| + +.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png + :alt: octocat + :width: 23 + +"""" + +.. [#f1] **Warning, use mentioned middlewares overridings with caution!** Overridden middleware removes the default implementation! If you are seeing any exceptions or strange behavior in your middleware pipeline, remove overridden middlewares and try again! diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index c5475222f..5efab9f1d 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -1,49 +1,55 @@ Quality of Service ================== -Ocelot supports one QoS capability at the current time. You can set on a per Route basis if you want to use a circuit breaker when making requests to a downstream service. This uses an awesome -.NET library called Polly check them out `here `_. + Label: `QoS `_ -The first thing you need to do if you want to use the administration API is bring in the relevant NuGet package: +Ocelot supports one QoS capability at the current time. You can set on a per Route basis if you want to use a circuit breaker when making requests to a downstream service. +This uses an awesome .NET library called `Polly `_, check them out `in official repo `_. - ``Install-Package Ocelot.Provider.Polly`` +The first thing you need to do if you want to use the :doc:`../features/administration` API is bring in the relevant NuGet `package `_: + +.. code-block:: powershell + + Install-Package Ocelot.Provider.Polly Then in your ``ConfigureServices`` method to add Polly services we must call the ``AddPolly()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: .. code-block:: csharp - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot() - .AddPolly(); - } + services.AddOcelot() + .AddPolly(); -Then add the following section to a Route configuration. +Then add the following section to a Route configuration: .. code-block:: json - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":1000, - "TimeoutValue":5000 - } + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 1000, + "TimeoutValue": 5000 + } -You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be implemented. Duration of break means the circuit breaker will stay open for 1 second after it is tripped. -TimeoutValue means if a request takes more than 5 seconds it will automatically be timed out. +- You must set a number greater than ``0`` against **ExceptionsAllowedBeforeBreaking** for this rule to be implemented. [#f2]_ +- **DurationOfBreak** means the circuit breaker will stay open for 1 second after it is tripped. +- **TimeoutValue** means if a request takes more than 5 seconds, it will automatically be timed out. -You can set the TimeoutValue in isolation of the ExceptionsAllowedBeforeBreaking and DurationOfBreak options. +You can set the **TimeoutValue** in isolation of the **ExceptionsAllowedBeforeBreaking** and **DurationOfBreak** options: .. code-block:: json - "QoSOptions": { - "TimeoutValue":5000 - } + "QoSOptions": { + "TimeoutValue": 5000 + } -There is no point setting the other two in isolation as they affect each other :) +There is no point setting the other two in isolation as they affect each other! -If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout on all downstream requests. If someone needs this to be configurable open an issue. +If you do not add a QoS section, QoS will not be used, however Ocelot will default to a **90** seconds timeout on all downstream requests. +If someone needs this to be configurable, open an issue. [#f2]_ """" .. [#f1] The ``AddOcelot`` method adds default ASP.NET services to DI-container. You could call another more extended ``AddOcelotUsingBuilder`` method while configuring services to build and use custom builder via an ``IMvcCoreBuilder`` interface object. See more instructions in :doc:`../features/dependencyinjection`, "**The AddOcelotUsingBuilder method**" section. +.. [#f2] If something doesn't work or you get stuck, please review current `QoS issues `_ filtering by |QoS_label| label. + +.. |QoS_label| image:: https://img.shields.io/badge/-QoS-D3ADAF.svg + :target: https://github.com/ThreeMammals/Ocelot/labels/QoS diff --git a/docs/features/ratelimiting.rst b/docs/features/ratelimiting.rst index 895b3e419..e0cf56690 100644 --- a/docs/features/ratelimiting.rst +++ b/docs/features/ratelimiting.rst @@ -1,47 +1,70 @@ Rate Limiting ============= -Thanks to `@catcherwong article `_ for inspiring me to finally write this documentation. +Ocelot Own Implementation +------------------------- -Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much. +Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. -OK so to get rate limiting working for a Route you need to add the following json to it. +The authors of this feature were inspired by `@catcherwong article `_ to finally write this documentation. +This feature was added by `@geffzhang `_ on GitHub! Thanks very much! + +To get rate limiting working for a Route you need to add the following JSON to it: .. code-block:: json - "RateLimitOptions": { - "ClientWhitelist": [], - "EnableRateLimiting": true, - "Period": "1s", - "PeriodTimespan": 1, - "Limit": 1 - } + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": true, + "Period": "1s", + "PeriodTimespan": 1, + "Limit": 1 + } -ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting. +* **ClientWhitelist** - This is an array that contains the whitelist of the client. + It means that the client in this array will not be affected by the rate limiting. +* **EnableRateLimiting** - This value specifies enable endpoint rate limiting. +* **Period** - This value specifies the period that the limit applies to, such as ``1s``, ``5m``, ``1h``, ``1d`` and so on. + If you make more requests in the period than the limit allows then you need to wait for **PeriodTimespan** to elapse before you make another request. +* **PeriodTimespan** - This value specifies that we can retry after a certain number of seconds. +* **Limit** - This value specifies the maximum number of requests that a client can make in a defined period. -EnableRateLimiting - This value specifies enable endpoint rate limiting. +You can also set the following in the **GlobalConfiguration** part of **ocelot.json**: -Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request. +.. code-block:: json -PeriodTimespan - This value specifies that we can retry after a certain number of seconds. + "GlobalConfiguration": { + "BaseUrl": "https://api.mybusiness.com", + "RateLimitOptions": { + "DisableRateLimitHeaders": false, + "QuotaExceededMessage": "Customize Tips!", + "HttpStatusCode": 123, + "ClientIdHeader": "Test" + } + } -Limit - This value specifies the maximum number of requests that a client can make in a defined period. +* **DisableRateLimitHeaders** - This value specifies whether ``X-Rate-Limit`` and ``Retry-After`` headers are disabled. +* **QuotaExceededMessage** - This value specifies the exceeded message. +* **HttpStatusCode** - This value specifies the returned HTTP status code when rate limiting occurs. +* **ClientIdHeader** - Allows you to specifiy the header that should be used to identify clients. By default it is ``ClientId`` -You can also set the following in the GlobalConfiguration part of ocelot.json +Future and ASP.NET Core Implementation +-------------------------------------- -.. code-block:: json +The Ocelot team considers to redesign *Rate Limiting* feature, +because of `Announcing Rate Limiting for .NET `_ by Brennan Conroy on July 13th, 2022. +There is no decision at the moment, and the old version of the feature is included as a part of release `20.0 `_ for .NET 7. - "RateLimitOptions": { - "DisableRateLimitHeaders": false, - "QuotaExceededMessage": "Customize Tips!", - "HttpStatusCode": 999, - "ClientIdHeader" : "Test" - } +See more about new feature being added into ASP.NET Core 7.0 release: -DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Retry-After headers are disabled. +* `RateLimiter Class `_, since ASP.NET Core **7.0** +* `System.Threading.RateLimiting `_ NuGet package +* `Rate limiting middleware in ASP.NET Core `_ article by Arvin Kahbazi, Maarten Balliauw, and Rick Anderson -QuotaExceededMessage - This value specifies the exceeded message. +However, it makes sense to keep the old implementation as a Ocelot built-in native feature, but we are going to migrate to the new Rate Limiter from ``Microsoft.AspNetCore.RateLimiting`` namespace. -HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs. +.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png + :alt: octocat + :width: 23 -ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId" +Please, share your opinion to us in the `Discussions `_ space of the repository. |octocat| diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index 76bc7a963..a07e7f982 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -1,71 +1,62 @@ Request Aggregation =================== -Ocelot allows you to specify Aggregate Routes that compose multiple normal Routes and map their responses into one object. This is usually where you have -a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type -architecture with Ocelot. +Ocelot allows you to specify Aggregate Routes that compose multiple normal Routes and map their responses into one object. +This is usually where you have a client that is making multiple requests to a server where it could just be one. +This feature allows you to start implementing back-end for a front-end (BFF) type architecture with Ocelot. -This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. +This feature was requested as part of `issue 79 `_ and further improvements were made as part of `issue 298 `_. -In order to set this up you must do something like the following in your **ocelot.json**. Here we have specified two normal Routes and each one has a Key property. -We then specify an Aggregate that composes the two Routes using their keys in the RouteKeys list and says then we have the UpstreamPathTemplate which works like a normal Route. -Obviously you cannot have duplicate UpstreamPathTemplates between Routes and Aggregates. You can use all of Ocelot's normal Route options apart from RequestIdKey -(explained in `gotchas <#gotchas--further-info>`_ below). +In order to set this up you must do something like the following in your **ocelot.json**. +Here we have specified two normal Routes and each one has a **Key** property. +We then specify an Aggregate that composes the two Routes using their keys in the **RouteKeys** list and says then we have the **UpstreamPathTemplate** which works like a normal Route. +Obviously you cannot have duplicate **UpstreamPathTemplates** between **Routes** and **Aggregates**. +You can use all of Ocelot's normal Route options apart from **RequestIdKey** (explained in `gotchas <#gotchas>`_ below). -Advanced register your own Aggregators +Advanced Register Your Own Aggregators -------------------------------------- Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the downstream services and then aggregate them into a response object. -The **ocelot.json** setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below: +The **ocelot.json** setup is pretty much the same as the basic aggregation approach apart from you need to add an **Aggregator** property like below: .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/laura", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51881 - } - ], - "Key": "Laura" - }, - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/tom", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51882 - } - ], - "Key": "Tom" - } + { + "Routes": [ + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/laura", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 51881 } ], - "Aggregates": [ - { - "RouteKeys": [ - "Tom", - "Laura" - ], - "UpstreamPathTemplate": "/", - "Aggregator": "FakeDefinedAggregator" - } - ] - } + "Key": "Laura" // <-- + }, + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/tom", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 51882 } + ], + "Key": "Tom" // <-- + } + ], + "Aggregates": [ + { + "UpstreamPathTemplate": "/", + "RouteKeys": [ + "Tom", + "Laura" + ], + "Aggregator": "FakeDefinedAggregator" + } + ] + } Here we have added an aggregator called ``FakeDefinedAggregator``. Ocelot is going to look for this aggregator when it tries to aggregate this Route. @@ -83,9 +74,8 @@ Because the ``FakeDefinedAggregator`` is registered in the container you can add .. code-block:: csharp services.AddSingleton(); - - services - .AddOcelot() + // ... + services.AddOcelot() .AddSingletonDefinedAggregator(); In this example ``FooAggregator`` takes a dependency on ``FooDependency`` and it will be resolved by the container. @@ -107,83 +97,74 @@ In order to make an Aggregator you must implement this interface: Task Aggregate(List responses); } -With this feature you can pretty much do whatever you want because the ``HttpContext`` objects contain the results of all the aggregate requests. Please note if the ``HttpClient`` throws -an exception when making a request to a Route in the aggregate then you will not get a ``HttpContext`` for it but you would for any that succeed. If it does throw an exception this will be logged. +With this feature you can pretty much do whatever you want because the ``HttpContext`` objects contain the results of all the aggregate requests. +Please note, if the ``HttpClient`` throws an exception when making a request to a Route in the aggregate then you will not get a ``HttpContext`` for it, but you would for any that succeed. +If it does throw an exception, this will be logged. -Basic expecting JSON from Downstream Services +Basic Expecting JSON from Downstream Services --------------------------------------------- .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/laura", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51881 - } - ], - "Key": "Laura" - }, - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/tom", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51882 - } - ], - "Key": "Tom" - } + { + "Routes": [ + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/laura", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 51881 } ], - "Aggregates": [ - { - "RouteKeys": [ - "Tom", - "Laura" - ], - "UpstreamPathTemplate": "/" - } + "Key": "Laura" + }, + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/tom", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 51882 } + ], + "Key": "Tom" + } + ], + "Aggregates": [ + { + "UpstreamPathTemplate": "/", + "RouteKeys": [ + "Tom", + "Laura" ] - } + } + ] + } -You can also set UpstreamHost and RouteIsCaseSensitive in the Aggregate configuration. These behave the same as any other Routes. +You can also set **UpstreamHost** and **RouteIsCaseSensitive** in the Aggregate configuration. These behave the same as any other Routes. -If the Route "**/tom**" returned a body of ``{"Age": 19}`` and "**/laura**" returned ``{"Age": 25}`` the the response after aggregation would be as follows: +If the Route ``/tom`` returned a body of ``{"Age": 19}`` and ``/laura`` returned ``{"Age": 25}``, the the response after aggregation would be as follows: .. code-block:: json {"Tom":{"Age": 19},"Laura":{"Age": 25}} -At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary -as above. With the Route key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just -JSON without any pretty spaces etc. +At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a JSON dictionary as above. +With the Route key being the key of the dictionary and the value the response body from your downstream service. +You can see that the object is just JSON without any pretty spaces etc. -All headers will be lost from the downstream services response. +Note, all headers will be lost from the downstream services response. -Ocelot will always return content type "**application/json**" with an aggregate request. +Ocelot will always return content type ``application/json`` with an aggregate request. -If you downstream services return a 404 the aggregate will just return nothing for that downstream service. -It will not change the aggregate response into a 404 even if all the downstreams return a 404. +If you downstream services return a `404 Not Found `_, the aggregate will just return nothing for that downstream service. +It will not change the aggregate response into a ``404`` even if all the downstreams return a ``404``. -Gotcha's / Further info ------------------------ +Gotchas +------- -You cannot use Routes with specific RequestIdKeys as this would be crazy complicated to track. +You cannot use Routes with specific **RequestIdKeys** as this would be crazy complicated to track. -Aggregation only supports the GET HTTP verb. +Aggregation only supports the ``GET`` HTTP verb. """" diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index d36734026..5fc46b936 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -1,40 +1,51 @@ -Request Id / Correlation Id -=========================== +Request ID +========== -Ocelot supports a client sending a request id in the form of a header. If set Ocelot willuse the requestid for logging as soon as it becomes available in the middleware pipeline. -Ocelot will also forward the request id with the specified header to the downstream service. + aka **Correlation ID** -You can still get the asp.net core request id in the logs if you set IncludeScopes true in your logging config. +Ocelot supports a client sending a *request ID* in the form of a header. +If set, Ocelot will use the **requestId** for logging as soon as it becomes available in the middleware pipeline. +Ocelot will also forward the *request ID* with the specified header to the downstream service. -In order to use the request id feature you have two options. +You can still get the ASP.NET Core *request ID* in the logs if you set **IncludeScopes** ``true`` in your logging config. -*Global* +In order to use the *Request ID* feature you have two options. -In your ocelot.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. +Global +------ + +In your **ocelot.json** set the following in the **GlobalConfiguration** section. This will be used for all requests into Ocelot. .. code-block:: json - "GlobalConfiguration": { + "GlobalConfiguration": { "RequestIdKey": "OcRequestId" } -I recommend using the GlobalConfiguration unless you really need it to be Route specific. +We recommend using the **GlobalConfiguration** unless you really need it to be Route specific. -*Route* +Route +----- -If you want to override this for a specific Route add the following to ocelot.json for the specific Route. +If you want to override this for a specific Route, add the following to **ocelot.json** for the specific Route: .. code-block:: json - "RequestIdKey": "OcRequestId" + "RequestIdKey": "OcRequestId" + +Once Ocelot has identified the incoming requests matching Route object it will set the *request ID* based on the Route configuration. -Once Ocelot has identified the incoming requests matching Route object it will set the request id based on the Route configuration. +Gotcha +------ -This can lead to a small gotcha. If you set a GlobalConfiguration it is possible to get one request id until the Route is identified and then another after that because the request id key can change. This is by design and is the best solution I can think of at the moment. In this case the OcelotLogger will show the request id and previous request id in the logs. +This can lead to a small gotcha. +If you set a **GlobalConfiguration**, it is possible to get one *request ID* until the Route is identified and then another after that because the *request ID* key can change. +This is by design and is the best solution we can think of at the moment. +In this case the ``OcelotLogger`` will show the *request ID* and previous *request ID* in the logs. -Below is an example of the logging when set at Debug level for a normal request.. +Below is an example of the logging when set at ``Debug`` level for a normal request: -.. code-block:: bash +.. code-block:: text dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] requestId: asdf, previousRequestId: no previous request id, message: ocelot pipeline started, diff --git a/docs/features/routing.rst b/docs/features/routing.rst index f1bd79375..10c4ba1e3 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -9,239 +9,208 @@ In order to get anything working in Ocelot you need to set up a Route in the con .. code-block:: json - { - "Routes": [ - ] - } + { + "Routes": [] + } -To configure a Route you need to add one to the Routes json array. +To configure a Route you need to add one to the Routes JSON array. .. code-block:: json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put", "Delete" ] - } + { + "UpstreamHttpMethod": [ "Put", "Delete" ], + "UpstreamPathTemplate": "/posts/{postId}", + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 80 } + ] + } -The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to. +The **DownstreamPathTemplate**, **DownstreamScheme** and **DownstreamHostAndPorts** define the URL that a request will be forwarded to. -DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to. Usually this will just contain a single entry but sometimes you might want to load balance requests to your downstream services and Ocelot allows you add more than one entry and then select a load balancer. +The **DownstreamHostAndPorts** property is a collection that defines the host and port of any downstream services that you wish to forward requests to. +Usually this will just contain a single entry, but sometimes you might want to load balance requests to your downstream services and Ocelot allows you add more than one entry and then select a load balancer. -The UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request. The UpstreamHttpMethod is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. You can set a specific list of HTTP Methods or set an empty list to allow any of them. +The **UpstreamPathTemplate** property is the URL that Ocelot will use to identify which **DownstreamPathTemplate** to use for a given request. +The **UpstreamHttpMethod** is used so Ocelot can distinguish between requests with different HTTP verbs to the same URL. +You can set a specific list of HTTP methods or set an empty list to allow any of them. -In Ocelot you can add placeholders for variables to your Templates in the form of {something}. The placeholder variable needs to be present in both the DownstreamPathTemplate and UpstreamPathTemplate properties. When it is Ocelot will attempt to substitute the value in the UpstreamPathTemplate placeholder into the DownstreamPathTemplate for each request Ocelot processes. +Placeholders +------------ -You can also do a catch all type of Route e.g. +In Ocelot you can add placeholders for variables to your Templates in the form of ``{something}``. +The placeholder variable needs to be present in both the **DownstreamPathTemplate** and **UpstreamPathTemplate** properties. +When it is Ocelot will attempt to substitute the value in the **UpstreamPathTemplate** placeholder into the **DownstreamPathTemplate** for each request Ocelot processes. -.. code-block:: json +You can also do a `Catch All <#catch-all>`_ type of Route e.g. - { - "DownstreamPathTemplate": "/api/{everything}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/{everything}", - "UpstreamHttpMethod": [ "Get", "Post" ] - } +.. code-block:: json -This will forward any path + query string combinations to the downstream service after the path /api. + { + "UpstreamHttpMethod": [ "Get", "Post" ], + "UpstreamPathTemplate": "/{everything}", + "DownstreamPathTemplate": "/api/{everything}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 80 } + ] + } +This will forward any path + query string combinations to the downstream service after the path ``/api``. -The default ReRouting configuration is case insensitive! +**Note**, the default Routing configuration is case insensitive! -In order to change this you can specify on a per Route basis the following setting. +In order to change this you can specify on a per Route basis the following setting: .. code-block:: json - "RouteIsCaseSensitive": true + "RouteIsCaseSensitive": true -This means that when Ocelot tries to match the incoming upstream url with an upstream template the -evaluation will be case sensitive. +This means that when Ocelot tries to match the incoming upstream URL with an upstream template the evaluation will be case sensitive. Catch All -^^^^^^^^^ +--------- -Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic. +Ocelot's routing also supports a *Catch All* style routing where the user can specify that they want to match all traffic. -If you set up your config like below, all requests will be proxied straight through. The placeholder {url} name is not significant, any name will work. +If you set up your config like below, all requests will be proxied straight through. +The placeholder ``{url}`` name is not significant, any name will work. .. code-block:: json - { - "DownstreamPathTemplate": "/{url}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/{url}", - "UpstreamHttpMethod": [ "Get" ] - } + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/{url}", + "DownstreamPathTemplate": "/{url}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 80 } + ] + } -The catch all has a lower priority than any other Route. If you also have the Route below in your config then Ocelot would match it before the catch all. +The *Catch All* has a lower priority than any other Route. +If you also have the Route below in your config then Ocelot would match it before the *Catch All*. .. code-block:: json - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ] - } + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { "Host": "10.0.10.1", "Port": 80 } + ] + } Upstream Host -^^^^^^^^^^^^^ +------------- -This feature allows you to have Routes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a Route. +This feature allows you to have Routes based on the *upstream host*. +This works by looking at the ``Host`` header the client has used and then using this as part of the information we use to identify a Route. -In order to use this feature please add the following to your config. +In order to use this feature please add the following to your config: .. code-block:: json - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ], - "UpstreamHost": "somedomain.com" - } + { + "UpstreamHost": "somedomain.com" + } -The Route above will only be matched when the host header value is somedomain.com. +The Route above will only be matched when the ``Host`` header value is ``somedomain.com``. -If you do not set UpstreamHost on a Route then any host header will match it. This means that if you have two Routes that are the same, apart from the UpstreamHost, where one is null and the other set Ocelot will favour the one that has been set. +If you do not set **UpstreamHost** on a Route then any ``Host`` header will match it. +This means that if you have two Routes that are the same, apart from the **UpstreamHost**, where one is null and the other set Ocelot will favour the one that has been set. -This feature was requested as part of `Issue 216 `_ . +This feature was requested as part of `issue 216 `_. Priority -^^^^^^^^ +-------- -You can define the order you want your Routes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json -See `Issue 270 `_ for reference +You can define the order you want your Routes to match the Upstream ``HttpRequest`` by including a **Priority** property in **ocelot.json**. +See `issue 270 `_ for reference. .. code-block:: json - { - "Priority": 0 - } + { + "Priority": 0 + } -0 is the lowest priority, Ocelot will always use 0 for /{catchAll} Routes and this is still hardcoded. After that you are free to set any priority you wish. +``0`` is the lowest priority, Ocelot will always use ``0`` for ``/{catchAll}`` Routes and this is still hardcoded. +After that you are free to set any priority you wish. e.g. you could have .. code-block:: json - { - "UpstreamPathTemplate": "/goods/{catchAll}" - "Priority": 0 - } + { + "UpstreamPathTemplate": "/goods/{catchAll}", + "Priority": 0 + } -and +and .. code-block:: json - { - "UpstreamPathTemplate": "/goods/delete" - "Priority": 1 - } + { + "UpstreamPathTemplate": "/goods/delete", + "Priority": 1 + } -In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete Route. Previously it would have matched /goods/{catchAll} (because this is the first Route in the list!). +In the example above if you make a request into Ocelot on ``/goods/delete``, Ocelot will match ``/goods/delete`` Route. +Previously it would have matched ``/goods/{catchAll}``, because this is the first Route in the list! Dynamic Routing -^^^^^^^^^^^^^^^ +--------------- This feature was requested in `issue 340 `_. -The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the Route config. See the docs :ref:`service-discovery` if -this sounds interesting to you. +The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the Route config. +See the docs :doc:`../features/servicediscovery` if this sounds interesting to you. Query Strings -^^^^^^^^^^^^^ +------------- -Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below. +Ocelot allows you to specify a query string as part of the **DownstreamPathTemplate** like the example below: .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 50110 - } - ] - } - ], - "GlobalConfiguration": { - } - } + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 50110 } + ] + } -In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! +In this example Ocelot will use the value from the ``{unitId}`` placeholder in the upstream path template and add it to the downstream request as a query string parameter called ``unitId``! -Ocelot will also allow you to put query string parameters in the UpstreamPathTemplate so you can match certain queries to certain services. +Ocelot will also allow you to put query string parameters in the **UpstreamPathTemplate** so you can match certain queries to certain services: .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", - "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 50110 - } - ] - } - ], - "GlobalConfiguration": { - } - } + { + "UpstreamHttpMethod": [ "Get" ], + "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 50110 } + ] + } -In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this -but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path. +In this example Ocelot will only match requests that have a matching URL path and the query string starts with ``unitId=something``. +You can have other queries after this but you must start with the matching parameter. +Also Ocelot will swap the ``{unitId}`` parameter from the query string and use it in the downstream request path. Security Options -^^^^^^^^^^^^^^^^ +---------------- -Ocelot allows you to manage multiple patterns for allowed/blocked IPs using the `IPAddressRange `_ package with `MPL-2.0 License `_. +Ocelot allows you to manage multiple patterns for allowed/blocked IPs using the `IPAddressRange `_ package +with `MPL-2.0 License `_. This feature is designed to allow greater IP management in order to include or exclude a wide IP range via CIDR notation or IP range. The current patterns managed are the following: @@ -253,35 +222,18 @@ The current patterns managed are the following: * CIDR: :code:`192.168.1.0/24` * CIDR for IPv6: :code:`fe80::/10` * The allowed/blocked lists are evaluated during configuration loading -* The *ExcludeAllowedFromBlocked* property is intended to provide the ability to specify a wide range of blocked IP addresses and allow a subrange of IP addresses. +* The **ExcludeAllowedFromBlocked** property is intended to provide the ability to specify a wide range of blocked IP addresses and allow a subrange of IP addresses. Default value: :code:`false` * The absence of a property in **SecurityOptions** is allowed, it takes the default value. .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/api/service/{Id}", - "UpstreamPathTemplate": "/api/internal-service/{Id}/full", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 50110 - } - ], - "SecurityOptions": { - "IPBlockedList": [ "192.168.0.0/23" ], - "IPAllowedList": ["192.168.0.15", "192.168.1.15"], - "ExcludeAllowedFromBlocked": true - }, - }, - ], - "GlobalConfiguration": { } + { + "SecurityOptions": { + "IPBlockedList": [ "192.168.0.0/23" ], + "IPAllowedList": ["192.168.0.15", "192.168.1.15"], + "ExcludeAllowedFromBlocked": true } + } -This feature was requested in the `issue 1400 `_. +This feature was requested as part of `issue 1400 `_. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 504ecbfb9..9089240e8 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -9,9 +9,9 @@ At the moment this is only supported in the **GlobalConfiguration** section, whi Consul ------ - | **Namespace**: `Ocelot.Provider.Consul `_ + | **Namespace**: `Ocelot.Provider.Consul `_ -The first thing you need to do is install `the NuGet package `_ that provides `Consul `_ support in Ocelot. +The first thing you need to do is install the `Ocelot.Provider.Consul `__ package that provides `Consul `_ support in Ocelot: .. code-block:: powershell @@ -21,11 +21,8 @@ Then add the following to your ``ConfigureServices`` method: .. code-block:: csharp - ConfigureServices(services => - { - services.AddOcelot() - .AddConsul(); - }); + services.AddOcelot() + .AddConsul(); Currently there are 2 types of Consul *service discovery* providers: ``Consul`` and ``PollConsul``. The default provider is ``Consul``, which means that if ``ConsulProviderFactory`` cannot read, understand, or parse the **Type** property of the ``ServiceProviderConfiguration`` object, then a ``Consul`` provider instance is created by the factory. @@ -40,37 +37,33 @@ Consul Provider Type The following is required in the `GlobalConfiguration `_. The **ServiceDiscoveryProvider** property is required, and if you do not specify a host and port, the Consul default ones will be used. -Please note the `Scheme `_ option defaults to HTTP. -It was added in `PR 1154 `_. It defaults to HTTP to not introduce a breaking change. +Please note the **Scheme** option defaults to ``HTTP``. +It was added in `PR 1154 `_. It defaults to ``HTTP`` to not introduce a breaking change. .. code-block:: json - "ServiceDiscoveryProvider": { - "Scheme": "https", - "Host": "localhost", - "Port": 8500, - "Type": "Consul" - } + "ServiceDiscoveryProvider": { + "Scheme": "https", + "Host": "localhost", + "Port": 8500, + "Type": "Consul" + } -In the future we can add a feature that allows Route specfic configuration. +In the future we can add a feature that allows Route specific configuration. -In order to tell Ocelot a Route is to use the *service discovery* provider for its host and port you must add the ServiceName and load balancer you wish to use when making requests downstream. +In order to tell Ocelot a Route is to use the *service discovery* provider for its host and port you must add the **ServiceName** and load balancer you wish to use when making requests downstream. At the moment Ocelot has a `RoundRobin `_ -and `LeastConnection `_ algorithm you can use. -If no load balancer is specified Ocelot will not load balance requests. +and `LeastConnection `_ algorithms you can use. +If no load balancer is specified, Ocelot will not load balance requests. .. code-block:: json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, + { + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" } + } When this is set up Ocelot will lookup the downstream host and port from the *service discovery* provider and load balance requests across any available services. @@ -79,29 +72,33 @@ PollConsul Provider Type | **Class**: `Ocelot.Provider.Consul.PollConsul `_ -A lot of people have asked me to implement a feature where Ocelot *polls Consul* for latest service information rather than per request. +A lot of people have asked the team to implement a feature where Ocelot *polls Consul* for latest service information rather than per request. If you want to *poll Consul* for the latest services rather than per request (default behaviour) then you need to set the following configuration: .. code-block:: json - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Type": "PollConsul", - "PollingInterval": 100 - } + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8500, + "Type": "PollConsul", + "PollingInterval": 100 + } The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration. -Please note there are tradeoffs here. If you *poll Consul* it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. This really depends on how volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling Consul per request (as sidecar agent). If you are calling a remote Consul agent then polling will be a good performance improvement. +Please note, there are tradeoffs here. +If you *poll Consul* it is possible Ocelot will not know if a service is down depending on your polling interval and you might get more errors than if you get the latest services per request. +This really depends on how volatile your services are. +We doubt it will matter for most people and polling may give a tiny performance improvement over calling Consul per request (as sidecar agent). +If you are calling a remote Consul agent then polling will be a good performance improvement. Service Definition ^^^^^^^^^^^^^^^^^^ Your services need to be added to Consul something like below (C# style but hopefully this make sense)... -The only important thing to note is not to add ``http`` or ``https`` to the Address field. -I have been contacted before about not accepting scheme in Address and accepting scheme in address. -After reading `this `_ I don't think the scheme should be in there. +The only important thing to note is not to add ``http`` or ``https`` to the ``Address`` field. +We have been contacted before about not accepting scheme in ``Address``. +After reading `this `_ we do not think the scheme should be in there. In C# @@ -119,172 +116,186 @@ Or, in JSON .. code-block:: json - "Service": { - "ID": "some-id", - "Service": "some-service-name", - "Address": "localhost", - "Port": 8080 - } + "Service": { + "ID": "some-id", + "Service": "some-service-name", + "Address": "localhost", + "Port": 8080 + } ACL Token ^^^^^^^^^ -If you are using `ACL `_ with Consul, Ocelot supports adding the "X-Consul-Token" header. +If you are using `ACL `_ with Consul, Ocelot supports adding the ``X-Consul-Token`` header. In order so this to work you must add the additional property below: .. code-block:: json - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Token": "footoken", - "Type": "Consul" - } + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8500, + "Type": "Consul", + "Token": "footoken" + } Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request. Eureka ------ -This feature was requested as part of `Issue 262 `_ . to add support for Netflix's Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe `_ which is something to do with `Pivotal `_! Anyway enough of the background. +This feature was requested as part of `issue 262 `_ to add support for `Netflix Eureka `_ service discovery provider. +The main reason for this is it is a key part of `Steeltoe `_ which is something to do with `Pivotal `_! +Anyway enough of the background. -The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot. +The first thing you need to do is install the `Ocelot.Provider.Eureka `_ package that provides Eureka support in Ocelot: .. code-block:: powershell Install-Package Ocelot.Provider.Eureka -Then add the following to your ConfigureServices method. +Then add the following to your ``ConfigureServices`` method. .. code-block:: csharp - s.AddOcelot() - .AddEureka(); + s.AddOcelot().AddEureka(); -Then in order to get this working add the following to ocelot.json.. +Then in order to get this working add the following to **ocelot.json**: .. code-block:: json - "ServiceDiscoveryProvider": { - "Type": "Eureka" - } + "ServiceDiscoveryProvider": { + "Type": "Eureka" + } -And following the guide `Here `_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it. +And following the guide `here `_ you may also need to add some stuff to **appsettings.json**. +For example the JSON below tells the Steeltoe / Pivotal services where to look for the service discovery server and if the service should register with it: .. code-block:: json - "eureka": { - "client": { - "serviceUrl": "http://localhost:8761/eureka/", - "shouldRegisterWithEureka": false, - "shouldFetchRegistry": true - } + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": false, + "shouldFetchRegistry": true } + } -I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there. +If **shouldRegisterWithEureka** is ``false`` then **shouldFetchRegistry** will defaut to ``true``, so you need not it explicitly but left it in there. -Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory. When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work. +Ocelot will now register all the necessary services when it starts up and if you have the JSON above will register itself with Eureka. +One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory. +When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. -Ocelot will use the scheme (http/https) set in Eureka if these values are not provided in ocelot.json +Ocelot will use the scheme (``http``, ``https``) set in Eureka if these values are not provided in **ocelot.json** Dynamic Routing --------------- -This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. +This feature was requested in `issue 340 `_. +The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). +In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. -An example of this would be calling Ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of -the path which is product and use it as a key to look up the service in Consul. If Consul returns a service Ocelot will request it on whatever host and port comes back from Consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. +An example of this would be calling Ocelot with a URL like ``https://api.mywebsite.com/product/products``. +Ocelot will take the first segment of the path which is ``product`` and use it as a key to look up the service in Consul. +If Consul returns a service, Ocelot will request it on whatever host and port comes back from Consul +plus the remaining path segments in this case products thus making the downstream call ``http://hostfromconsul:portfromconsul/products``. +Ocelot will apprend any query string to the downstream URL as normal. -In order to enable dynamic routing you need to have 0 Routes in your config. At the moment you cannot mix dynamic and configuration Routes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme. +**Note**, in order to enable dynamic routing you need to have ``0`` Routes in your config. +At the moment you cannot mix dynamic and configuration Routes. +In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream ``http``/``https`` scheme as **DownstreamScheme**. -In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic Routes. +In addition to that you can set **RateLimitOptions**, **QoSOptions**, **LoadBalancerOptions** and **HttpHandlerOptions**, **DownstreamScheme** +(You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic Routes. -The config might look something like +The config might look something like: .. code-block:: json - { - "Routes": [], - "Aggregates": [], - "GlobalConfiguration": { - "RequestIdKey": null, - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Type": "Consul", - "Token": null, - "ConfigurationKey": null - }, - "RateLimitOptions": { - "ClientIdHeader": "ClientId", - "QuotaExceededMessage": null, - "RateLimitCounterPrefix": "ocelot", - "DisableRateLimitHeaders": false, - "HttpStatusCode": 429 - }, - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 0, - "DurationOfBreak": 0, - "TimeoutValue": 0 - }, - "BaseUrl": null, - "LoadBalancerOptions": { - "Type": "LeastConnection", - "Key": null, - "Expiry": 0 - }, - "DownstreamScheme": "http", - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - "UseCookieContainer": false, - "UseTracing": false - } - } + { + "Routes": [], + "Aggregates": [], + "GlobalConfiguration": { + "RequestIdKey": null, + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8500, + "Type": "Consul", + "Token": null, + "ConfigurationKey": null + }, + "RateLimitOptions": { + "ClientIdHeader": "ClientId", + "QuotaExceededMessage": null, + "RateLimitCounterPrefix": "ocelot", + "DisableRateLimitHeaders": false, + "HttpStatusCode": 429 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 0, + "DurationOfBreak": 0, + "TimeoutValue": 0 + }, + "BaseUrl": null, + "LoadBalancerOptions": { + "Type": "LeastConnection", + "Key": null, + "Expiry": 0 + }, + "DownstreamScheme": "http", + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + "UseCookieContainer": false, + "UseTracing": false + } } + } -Ocelot also allows you to set DynamicRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows. +Ocelot also allows you to set **DynamicRoutes** collection which lets you set rate limiting rules per downstream service. +This is useful if you have for example a product and search service and you want to rate limit one more than the other. +An example of this would be as follows: .. code-block:: json - { - "DynamicRoutes": [ - { - "ServiceName": "product", - "RateLimitRule": { - "ClientWhitelist": [], - "EnableRateLimiting": true, - "Period": "1s", - "PeriodTimespan": 1000.0, - "Limit": 3 - } - } - ], - "GlobalConfiguration": { - "RequestIdKey": null, - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8523, - "Type": "Consul" - }, - "RateLimitOptions": { - "ClientIdHeader": "ClientId", - "QuotaExceededMessage": "", - "RateLimitCounterPrefix": "", - "DisableRateLimitHeaders": false, - "HttpStatusCode": 428 - } - "DownstreamScheme": "http", + { + "DynamicRoutes": [ + { + "ServiceName": "product", + "RateLimitRule": { + "ClientWhitelist": [], + "EnableRateLimiting": true, + "Period": "1s", + "PeriodTimespan": 1000.0, + "Limit": 3 } + } + ], + "GlobalConfiguration": { + "RequestIdKey": null, + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8523, + "Type": "Consul" + }, + "RateLimitOptions": { + "ClientIdHeader": "ClientId", + "QuotaExceededMessage": "", + "RateLimitCounterPrefix": "", + "DisableRateLimitHeaders": false, + "HttpStatusCode": 428 + }, + "DownstreamScheme": "http" } + } -This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicRoutes section. +This configuration means that if you have a request come into Ocelot on ``/product/*`` then dynamic routing will kick in and Ocelot will use the rate limiting set against the product service in the **DynamicRoutes** section. Please take a look through all of the docs to understand these options. Custom Providers ---------------- -Ocelot also allows you to create your own ServiceDiscovery implementation. +Ocelot also allows you to create your own *Service Discovery* implementation. This is done by implementing the ``IServiceDiscoveryProvider`` interface, as shown in the following example: .. code-block:: csharp @@ -333,8 +344,8 @@ Custom Provider Sample In order to introduce a basic template for a custom Service Discovery provider, we've prepared a good sample: - | **Link**: `samples <../../samples>`_ / `OcelotServiceDiscovery <../../samples/OcelotServiceDiscovery>`_ - | **Solution**: `Ocelot.Samples.ServiceDiscovery.sln <../../samples/OcelotServiceDiscovery/Ocelot.Samples.ServiceDiscovery.sln>`_ + | **Link**: `samples `_ / `OcelotServiceDiscovery `_ + | **Solution**: `Ocelot.Samples.ServiceDiscovery.sln `_ This solution contains the following projects: @@ -343,7 +354,7 @@ This solution contains the following projects: This solution is ready for any deployment. All services are bound, meaning all ports and hosts are prepared for immediate use (running in Visual Studio). -All instructions for running this solution are in `README.md <../../samples/OcelotServiceDiscovery/README.md>`_. +All instructions for running this solution are in `README.md `_. DownstreamService """"""""""""""""" @@ -354,11 +365,12 @@ It has multiple **launchSettings.json** profiles for your favorite launch and ho ApiGateway """""""""" -This project includes a custom Service Discovery provider and it only has route(s) to `DownstreamService <#downstreamservice>`_ services in the **ocelot.json** file. +This project includes a custom *Service Discovery* provider and it only has route(s) to `DownstreamService <#downstreamservice>`_ services in the **ocelot.json** file. You can add more routes! -The main source code for the custom provider is in the `ServiceDiscovery <../../samples/OcelotServiceDiscovery/ApiGateway/ServiceDiscovery>`_ folder: -the ``MyServiceDiscoveryProvider`` and ``MyServiceDiscoveryProviderFactory`` classes. You are welcome to design and develop them! +The main source code for the custom provider is in the `ServiceDiscovery `_ folder: +the ``MyServiceDiscoveryProvider`` and ``MyServiceDiscoveryProviderFactory`` classes. +You are welcome to design and develop them! Additionally, the cornerstone of this custom provider is the ``ConfigureServices`` method, where you can choose design and implementation options: simple or more complex: @@ -392,7 +404,7 @@ Additionally, the cornerstone of this custom provider is the ``ConfigureServices The easy way, lite design means that you only design the provider class, and specify ``ServiceDiscoveryFinderDelegate`` object for default ``ServiceDiscoveryProviderFactory`` in Ocelot core. A more complex design means that you design both provider and provider factory classes. -After this, you need to add the ``IServiceDiscoveryProviderFactory`` interface to the DI-container, removing the default registered ``ServiceDiscoveryProviderFactory`` class. -Note that in this case the Ocelot core will not use ``ServiceDiscoveryProviderFactory`` by default. +After this, you need to add the ``IServiceDiscoveryProviderFactory`` interface to the DI container, removing the default registered ``ServiceDiscoveryProviderFactory`` class. +Note that in this case the Ocelot pipeline will not use ``ServiceDiscoveryProviderFactory`` by default. Additionally, you do not need to specify ``"Type": "MyServiceDiscoveryProvider"`` in the **ServiceDiscoveryProvider** properties of the **GlobalConfiguration** settings. But you can leave this ``Type`` option for compatibility between both designs. diff --git a/docs/features/servicefabric.rst b/docs/features/servicefabric.rst index 64e62d191..6e3906bd0 100644 --- a/docs/features/servicefabric.rst +++ b/docs/features/servicefabric.rst @@ -1,40 +1,41 @@ Service Fabric ============== -If you have services deployed in Service Fabric you will normally use the naming service to access them. +If you have services deployed in `Azure Service Fabric `_ you will normally use the naming service to access them. -The following example shows how to set up a Route that will work in Service Fabric. The most important thing is the ServiceName which is made up of the Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081. +The following example shows how to set up a Route that will work in *Service Fabric*. +The most important thing is the **ServiceName** which is made up of the *Service Fabric* application name then the specific service name. +We also need to set up the **ServiceDiscoveryProvider** in **GlobalConfiguration**. +The example here shows a typical configuration. +It assumes *Service Fabric* is running on ``localhost`` and that the naming service is on port ``19081``. -The example below is taken from the samples folder so please check it if this doesnt make sense! +The example below is taken from the `samples/OcelotServiceFabric `_ folder so please check it if this doesn't make sense! .. code-block:: json - { - "Routes": [ - { - "DownstreamPathTemplate": "/api/values", - "UpstreamPathTemplate": "/EquipmentInterfaces", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "ServiceName": "OcelotServiceApplication/OcelotApplicationService", - } - ], - "GlobalConfiguration": { - "RequestIdKey": "OcRequestId", - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 19081, - "Type": "ServiceFabric" - } - } + { + "Routes": [ + { + "DownstreamScheme": "http", + "DownstreamPathTemplate": "/api/values", + "UpstreamPathTemplate": "/EquipmentInterfaces", + "UpstreamHttpMethod": [ "Get" ], + "ServiceName": "OcelotServiceApplication/OcelotApplicationService" + } + ], + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId", + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 19081, + "Type": "ServiceFabric" + } } + } -If you are using stateless / guest exe services ocelot will be able to proxy through the naming service without anything else. However -if you are using statefull / actor services you must send the PartitionKind and PartitionKey query string values with the client -request e.g. +If you are using stateless / guest exe services, Ocelot will be able to proxy through the naming service without anything else. +However, if you are using statefull / actor services, you must send the **PartitionKind** and **PartitionKey** query string values with the client request e.g. -GET http://ocelot.com/EquipmentInterfaces?PartitionKind=xxx&PartitionKey=xxx + GET http://ocelot.com/EquipmentInterfaces?PartitionKind=xxx&PartitionKey=xxx -There is no way for Ocelot to work these out for you. +There is no way for Ocelot to work these out for you. diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index 311b91939..4614917e2 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -3,14 +3,18 @@ Tracing This page details how to perform distributed tracing with Ocelot. -OpenTracing ------------ +.. |opentracing-csharp Logo| image:: https://avatars.githubusercontent.com/u/15482765 + :alt: opentracing-csharp Logo + :width: 30 -Ocelot providers tracing functionality from the excellent `OpenTracing C# `_ project. -The code for the Ocelot integration can be found `here `_. +|opentracing-csharp Logo| OpenTracing +------------------------------------- -The example below uses `Jaeger C# `_ client to provide the tracer used in Ocelot. -In order to add OpenTracing services we must call the ``AddOpenTracing()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: +Ocelot providers tracing functionality from the excellent `OpenTracing API for .NET `_ project. +The code for the Ocelot integration can be found in `Ocelot.Tracing.OpenTracing `_ project. + +The example below uses `C# Client for Jaeger `_ client to provide the tracer used in Ocelot. +In order to add `OpenTracing `_ services we must call the ``AddOpenTracing()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: .. code-block:: csharp @@ -32,34 +36,42 @@ Then in your **ocelot.json** add the following to the Route you want to trace: .. code-block:: json - "HttpHandlerOptions": { - "UseTracing": true - }, + "HttpHandlerOptions": { + "UseTracing": true + } + +Ocelot will now send tracing information to `Jaeger `_ when this Route is called. -Ocelot will now send tracing information to Jaeger when this Route is called. +OpenTracing Status +^^^^^^^^^^^^^^^^^^ + +The `OpenTracing `_ project was archived on January 31, 2022 (see `the article `_). +The Ocelot team will decide on a migration to `OpenTelemetry `_ which is highly desired. Butterfly --------- -Ocelot providers tracing functionality from the excellent `Butterfly `_ project. The code for the Ocelot integration -can be found `here `_. +Ocelot providers tracing functionality from the excellent `Butterfly `_ project. +The code for the Ocelot integration can be found in `Ocelot.Tracing.Butterfly `_ project. + +In order to use the tracing please read the `Butterfly `_ documentation. -In order to use the tracing please read the Butterfly documentation. +In Ocelot you need to add the `NuGet package `_ if you wish to trace a Route: -In ocelot you need to do the following if you wish to trace a Route. +.. code-block:: powershell - ``Install-Package Ocelot.Tracing.Butterfly`` + Install-Package Ocelot.Tracing.Butterfly -In your ``ConfigureServices`` method to add Butterfly services we must call the ``AddButterfly()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: +In your ``ConfigureServices`` method to add Butterfly services: we must call the ``AddButterfly()`` extension of the ``OcelotBuilder`` being returned by ``AddOcelot()`` [#f1]_ like below: .. code-block:: csharp services .AddOcelot() - // this comes from Ocelot.Tracing.Butterfly package + // This comes from Ocelot.Tracing.Butterfly package .AddButterfly(option => { - //this is the url that the butterfly collector server is running on... + // This is the URL that the Butterfly collector server is running on... option.CollectorUrl = "http://localhost:9618"; option.Service = "Ocelot"; }); @@ -68,9 +80,9 @@ Then in your **ocelot.json** add the following to the Route you want to trace: .. code-block:: json - "HttpHandlerOptions": { - "UseTracing": true - }, + "HttpHandlerOptions": { + "UseTracing": true + } Ocelot will now send tracing information to Butterfly when this Route is called. diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 85271c30f..8dfc23321 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -1,11 +1,13 @@ Websockets ========== -Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 `_. + `WebSockets Standard `_ by WHATWG organization -In order to get websocket proxying working with Ocelot you need to do the following. +Ocelot supports proxying `WebSockets `_ with some extra bits. +This functionality was requested in `issue 212 `_. -In your ``Configure`` method you need to tell your application to use WebSockets: +In order to get *WebSocket* proxying working with Ocelot you need to do the following. +In your ``Configure`` method you need to tell your application to use *WebSockets*: .. code-block:: csharp @@ -15,95 +17,137 @@ In your ``Configure`` method you need to tell your application to use WebSockets app.UseOcelot().Wait(); }) -Then in your **ocelot.json** add the following to proxy a Route using WebSockets: +Then in your **ocelot.json** add the following to proxy a Route using *WebSockets*: .. code-block:: json { - "DownstreamPathTemplate": "/ws", "UpstreamPathTemplate": "/", + "DownstreamPathTemplate": "/ws", "DownstreamScheme": "ws", "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 5001 - } - ], + { "Host": "localhost", "Port": 5001 } + ] } -With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. +With this configuration set Ocelot will match any *WebSocket* traffic that comes in on / and proxy it to ``localhost:5001/ws``. +To make this clearer Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. + +Links +----- + +* WHATWG: `WebSockets Standard `_ +* Mozilla Developer Network: `The WebSocket API (WebSockets) `_ +* Microsoft Learn: `WebSockets support in ASP.NET Core `_ +* Microsoft Learn: `WebSockets support in .NET `_ SignalR -^^^^^^^ +------- + + Welcome to `Real-time ASP.NET with SignalR `_ -Ocelot supports proxying SignalR. This functionality was requested in `Issue 344 `_. +Ocelot supports proxying *SignalR*. +This functionality was requested in `issue 344 `_. +In order to get *WebSocket* proxying working with Ocelot you need to do the following. -In order to get websocket proxying working with Ocelot you need to do the following. +**First**, install `SignalR Client `_ NuGet package: -Install Microsoft.AspNetCore.SignalR.Client 1.0.2 you can try other packages but this one is tested. +.. code-block:: powershell -Do not run it in IISExpress or install the websockets feature in the IIS features + NuGet\Install-Package Microsoft.AspNetCore.SignalR.Client -In your Configure method you need to tell your application to use SignalR. +The package is deprecated, but `new versions `_ are still built from the source code. +So, SignalR is `the part `_ of the ASP.NET Framework which can be referenced like: + +.. code-block:: xml + + + + + +More information on framework compatibility can be found in instrictions: `Use ASP.NET Core APIs in a class library `_. + +**Second**, you need to tell your application to use *SignalR*. +Complete reference is here: `ASP.NET Core SignalR configuration `_ .. code-block:: csharp - Configure(app => + public void ConfigureServices(IServiceCollection services) { - app.UseWebSockets(); - app.UseOcelot().Wait(); - }) + services.AddOcelot(); + services.AddSignalR(); + } + +Pay attention to configuration of transport level of *WebSockets*, +so `configure allowed transports `_ to allow *WebSockets* connections. -Then in your ocelot.json add the following to proxy a Route using SignalR. Note normal Ocelot routing rules apply the main thing is the scheme which is set to "ws". +**Then** in your **ocelot.json** add the following to proxy a Route using SignalR. +Note normal Ocelot routing rules apply the main thing is the scheme which is set to ``ws``. .. code-block:: json { - "Routes": [ - { - "DownstreamPathTemplate": "/{catchAll}", - "DownstreamScheme": "ws", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 5001 - } - ], - "UpstreamPathTemplate": "/gateway/{catchAll}", - "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ] - } + "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ], + "UpstreamPathTemplate": "/gateway/{catchAll}", + "DownstreamPathTemplate": "/{catchAll}", + "DownstreamScheme": "ws", + "DownstreamHostAndPorts": [ + { "Host": "localhost", "Port": 5001 } ] } -With this configuration set Ocelot will match any SignalR traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. - Supported -^^^^^^^^^ +--------- -1. Load Balancer -2. Routing -3. Service Discovery +1. :doc:`../features/loadbalancer` +2. :doc:`../features/routing` +3. :doc:`../features/servicediscovery` -This means that you can set up your downstream services running websockets and either have multiple DownstreamHostAndPorts in your Route config or hook your Route into a service discovery provider and then load balance requests...Which I think is pretty cool :) +This means that you can set up your downstream services running *WebSockets* and either have multiple **DownstreamHostAndPorts** in your Route config, +or hook your Route into a service discovery provider and then load balance requests... Which we think is pretty cool. Not Supported -^^^^^^^^^^^^^ +------------- + +Unfortunately a lot of Ocelot features are non *WebSocket* specific, such as header and http client stuff. +We have listed what will not work below: + +1. :doc:`../features/tracing` +2. :doc:`../features/requestid` +3. :doc:`../features/requestaggregation` +4. :doc:`../features/ratelimiting` +5. :doc:`../features/qualityofservice` +6. :doc:`../features/middlewareinjection` +7. :doc:`../features/headerstransformation` +8. :doc:`../features/delegatinghandlers` +9. :doc:`../features/claimstransformation` +10. :doc:`../features/caching` +11. :doc:`../features/authentication` [#f1]_ +12. :doc:`../features/authorization` + +We are not 100% sure what will happen with this feature when it gets into the wild, so please make sure you test thoroughly! + +Future +------ + +*Websockets* and *SignalR* are being developed intensively by the .NET community, so you need to watch for trends, releases in official docs regularly: + +* `WebSockets docs `_ +* `SignalR docs `_ + +As a team, we cannot advise you on development, +but feel free to ask questions, get coding recipes in the `Discussions `_ space of the repository. |octocat| -Unfortunately a lot of Ocelot's features are non websocket specific such as header and http client stuff. I've listed what won't work below. +.. |octocat| image:: https://github.githubassets.com/images/icons/emoji/octocat.png + :alt: octocat + :width: 23 -1. Tracing -2. RequestId -3. Request Aggregation -4. Rate Limiting -5. Quality of Service -6. Middleware Injection -7. Header Transformation -8. Delegating Handlers -9. Claims Transformation -10. Caching -11. Authentication - If anyone requests it we might be able to do something with basic authentication. -12. Authorization +Also, we welcome any bug reports, enhancements or proposals regarding this feature. -I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly! +The Ocelot team considers the current impementation of WebSockets feature obsolete, based on the `WebSocketsProxyMiddleware `_ class. +Websockets are the part of ASP.NET Core framework having native `WebSocketMiddleware `_ class. +We have a strong intention to migrate or at least redesign the feature, see `issue 1707 `_. +"""" +.. [#f1] If anyone requests it, we might be able to do something with basic authentication. diff --git a/docs/index.rst b/docs/index.rst index 611dfb33a..38d6c0a87 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,13 @@ -Welcome to Ocelot -================= +Welcome to Ocelot 20.0 +====================== -Thanks for taking a look at the Ocelot documentation. Please use the left hand nav to get around. I would suggest taking a look at introduction first. +Thanks for taking a look at the Ocelot documentation! Please use the left hand navigation to get around. +The team would suggest taking a look at the **Introduction** chapter first. + +All **Features** are arranged in alphabetical order. +The main features are :doc:`../features/configuration` and :doc:`../features/routing`. + +We **do** follow development process which is described in :doc:`../building/releaseprocess`. .. toctree:: :maxdepth: 2 @@ -19,31 +25,31 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n :hidden: :caption: Features + features/administration + features/authentication + features/authorization + features/caching + features/claimstransformation features/configuration + features/delegatinghandlers features/dependencyinjection - features/routing - features/requestaggregation + features/errorcodes features/graphql - features/servicediscovery - features/servicefabric + features/headerstransformation features/kubernetes - features/authentication - features/authorization - features/websockets - features/administration - features/ratelimiting - features/caching - features/qualityofservice - features/headerstransformation - features/methodtransformation - features/claimstransformation + features/loadbalancer features/logging - features/tracing - features/requestid + features/methodtransformation features/middlewareinjection - features/loadbalancer - features/delegatinghandlers - features/errorcodes + features/qualityofservice + features/ratelimiting + features/requestaggregation + features/requestid + features/routing + features/servicediscovery + features/servicefabric + features/tracing + features/websockets .. toctree:: :maxdepth: 2 @@ -54,6 +60,3 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n building/building building/tests building/releaseprocess - - - diff --git a/docs/introduction/bigpicture.rst b/docs/introduction/bigpicture.rst index fbb5b12c9..d94c10446 100644 --- a/docs/introduction/bigpicture.rst +++ b/docs/introduction/bigpicture.rst @@ -1,13 +1,21 @@ Big Picture =========== -Ocelot is aimed at people using .NET running a microservices / service-oriented architecture that need a unified point of entry into their system. +Ocelot is aimed at people using .NET running a microservices / service-oriented architecture +that need a unified point of entry into their system. However it will work with anything that speaks HTTP(S) and run on any platform that ASP.NET Core supports. -In particular I want easy integration with IdentityServer reference and bearer tokens. +In particular we want easy integration with `IdentityServer `_ reference and `Bearer `_ tokens. +We have been unable to find this in our current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. +We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. -Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. +Ocelot manipulates the ``HttpRequest`` object into a state specified by its configuration until it reaches a request builder middleware, +where it creates a ``HttpRequestMessage`` object which is used to make a request to a downstream service. +The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. +The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline. +There is a piece of middleware that maps the ``HttpResponseMessage`` onto the ``HttpResponse`` object and that is returned to the client. +That is basically it with a bunch of other features! The following are configurations that you use when deploying Ocelot. diff --git a/docs/introduction/contributing.rst b/docs/introduction/contributing.rst index db05d5d92..a11f7d1c1 100644 --- a/docs/introduction/contributing.rst +++ b/docs/introduction/contributing.rst @@ -1,4 +1,8 @@ Contributing ============ -Pull requests, issues and commentary welcome! No special process just create a request and get in touch either via gitter or create an issue. \ No newline at end of file +Pull requests, issues and commentary welcome! + +Ideas, questions could be posted to Ocelot `Discussions `_ space. + +We do follow development process which is described in :doc:`../building/releaseprocess`. diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 86d580a36..efe296971 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -1,7 +1,7 @@ Getting Started =============== -Ocelot is designed to work with ASP.NET and is currently on ``net7.0``. +Ocelot is designed to work with ASP.NET and is currently on ``net7.0`` framework. .NET 7.0 -------- @@ -9,9 +9,9 @@ Ocelot is designed to work with ASP.NET and is currently on ``net7.0``. Install NuGet package ^^^^^^^^^^^^^^^^^^^^^ -Install Ocelot and it's dependencies using `nuget `_. -You will need to create a `net7.0 project `_ and bring the package into it. -Then follow the Startup below and :doc:`../features/configuration` sections to get up and running. +Install Ocelot and it's dependencies using `NuGet `_. +You will need to create a `ASP.NET Core 7.0 project `_ and bring the package into it. +Then follow the startup below and :doc:`../features/configuration` sections to get up and running. .. code-block:: powershell @@ -57,9 +57,15 @@ If you want some example that actually does something use the following: } } -The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. +The most important thing to note here is **BaseUrl** property. +Ocelot needs to know the URL it is running under in order to do Header find & replace and for certain administration configurations. +When setting this URL it should be the external URL that clients will see Ocelot running on e.g. +If you are running containers Ocelot might run on the URL ``http://123.12.1.1:6543`` but has something like **nginx** in front of it responding on ``https://api.mybusiness.com``. +In this case the Ocelot **BaseUrl** should be ``https://api.mybusiness.com``. -If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543 then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. +If you are using containers and require Ocelot to respond to clients on ``http://123.12.1.1:6543`` then you can do this, +however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. +Hopefully whatever scheduler you are using can pass the IP. Program ^^^^^^^ diff --git a/docs/introduction/gotchas.rst b/docs/introduction/gotchas.rst index e980e0370..e62e1eedb 100644 --- a/docs/introduction/gotchas.rst +++ b/docs/introduction/gotchas.rst @@ -1,4 +1,25 @@ Gotchas ============= -**Note:** When using ASP.NET Core 2.2 and you want to use In-Process hosting, replace **.UseIISIntegration()** with **.UseIIS()**, otherwise you'll get startup errors. \ No newline at end of file +IIS +----- + + Microsoft Learn: `Host ASP.NET Core on Windows with IIS `_ + +We do not recommend to deploy Ocelot app to IIS environments, but if you do, keep in mind the gotchas below. + +* When using ASP.NET Core 2.2+ and you want to use In-Process hosting, replace ``UseIISIntegration()`` with ``UseIIS()``, otherwise you will get startup errors. + +* Make sure you use Out-of-process hosting model instead of In-process one + (see `Out-of-process hosting with IIS and ASP.NET Core `_), + otherwise you will get very slow responses (see `1657 `_). + +* Ensure all DNS servers of all downstream hosts are online and they function perfectly, otherwise you will get slow responses (see `1630 `_). + +The community constanly reports `issues related to IIS `_. +If you have some troubles in IIS environment to host Ocelot app, first of all, read open/closed issues, and after that, search for `IIS `_ in the repository. +Probably you will find a ready solution by Ocelot community members. + +Finally, we have special label |IIS| for all IIS related objects. Feel free to put this label onto issues, PRs, discussions, etc. + +.. |IIS| image:: https://img.shields.io/badge/-IIS-c5def5.svg diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index 4d86759a0..7c7c9a5f0 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -3,11 +3,25 @@ Not Supported Ocelot does not support... -* Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case! +Chunked Encoding +---------------- + +Ocelot will always get the body size and return `Content-Length `_ header. +Sorry, if this doesn't work for your use case! -* Forwarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( +Forwarding a Host header +------------------------ + +The `Host `_ header that you send to Ocelot will not be forwarded to the downstream service. +Obviously this would break everything ๐Ÿ˜Ÿ + +Swagger +------- -* Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore +Contributors have looked multiple times at building **swagger.json** out of the Ocelot **ocelot.json** but it doesnt fit into the vision the team has for Ocelot. +If you would like to have Swagger in Ocelot then you must roll your own **swagger.json** and do the following in your **Startup.cs** or **Program.cs**. +The code sample below registers a piece of middleware that loads your hand rolled **swagger.json** and returns it on ``/swagger/v1/swagger.json``. +It then registers the SwaggerUI middleware from `Swashbuckle.AspNetCore `_ package: .. code-block:: csharp @@ -25,8 +39,16 @@ Ocelot does not support... app.UseOcelot().Wait(); -The main reasons why I don't think Swagger makes sense is we already hand roll our definition in ocelot.json. If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. +The main reasons why we don't think Swagger makes sense is we already hand roll our definition in **ocelot.json**. +If we want people developing against Ocelot to be able to see what routes are available then either share the **ocelot.json** with them +(This should be as easy as granting access to a repo etc) or use the Ocelot :doc:`../features/administration` API so that they can query Ocelot for the configuration. -In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to their product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. Unless I rolled my own Swagger implementation. +In addition to this, many people will configure Ocelot to proxy all traffic like ``/products/{everything}`` to their product service +and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. +Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. +Ocelot does not know what models might be used in POST, PUT etc, so it all gets a bit messy, and finally, the Swashbuckle package doesnt reload **swagger.json** if it changes during runtime. +Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. +Unless we rolled our own Swagger implementation. ๐Ÿ˜‹ -If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this. +If the developer wants something to easily test against the Ocelot API then we suggest using `Postman `_ as a simple way to do this. +It might even be possible to write something that maps **ocelot.json** to the Postman JSON spec. However we don't intend to do this. diff --git a/docs/make.bat b/docs/make.bat index 32bb24529..4a361ce13 100755 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,5 +1,4 @@ @ECHO OFF - pushd %~dp0 REM Command file for Sphinx documentation @@ -12,24 +11,32 @@ set BUILDDIR=_build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ + echo The 'sphinx-build' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the 'sphinx-build' executable. + echo Alternatively you may add the Sphinx directory to PATH. + echo If you don't have Sphinx installed, grab it from https://www.sphinx-doc.org/ exit /b 1 ) -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +set command="%1" &:: html, clean and etc. +call :dequote %command% +echo Doing %ret% ... + +IF %command% == "" ( + set status="FAILED" + echo There is no build command! Available commands: clean, html + echo See Sphinx Help below. + %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +) ELSE ( + %SPHINXBUILD% -M %command% %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + set status="DONE" +) +call :dequote %status% +echo Build %ret% -:end popd + +:dequote +setlocal +set thestring=%~1 +endlocal&set ret=%thestring% +goto :eof diff --git a/docs/make.sh b/docs/make.sh new file mode 100644 index 000000000..17c3d2787 --- /dev/null +++ b/docs/make.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Command file for Sphinx documentation +# +if [ "$SPHINXBUILD" == "" ] +then + SPHINXBUILD="sphinx-build" +fi + +SOURCEDIR="." +BUILDDIR="_build" + +command=$1 # html, clean and etc. +echo Doing $command ... +if [ "$command" == "" ] +then + status="FAILED" + echo There is no build command! Available commands: clean, html + echo See Sphinx Help below. + $SPHINXBUILD -M help $SOURCEDIR $BUILDDIR $SPHINXOPTS $O +else + $SPHINXBUILD -M $command $SOURCEDIR $BUILDDIR $SPHINXOPTS $O + status="DONE" +fi +echo Build $status diff --git a/docs/readme.md b/docs/readme.md index 1c1f4c4f0..5d71cac46 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,14 +1,14 @@ -# Ocelot documentation +# Ocelot Documentation -The folder contains the documentation for Ocelot. +The folder contains the documentation for Ocelot, build tools and configuration. -We are using [Read the docs](https://readthedocs.org/) to host the documentation and the rendered version -can be found [here](https://ocelot.readthedocs.io). +We are using [Read the Docs](https://about.readthedocs.com) to host the documentation and the rendered version can be found [here](https://ocelot.readthedocs.io). -Doc pages are authored in ReStructuredText (RST) - you can find a primer [here](http://www.sphinx-doc.org/en/stable/rest.html). +Doc pages are authored in [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html) (reST). +You can find a primer [here](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html). -You can find more information about RTD and Sphinx under the following links: - -* [Read the Docs documentation](https://docs.readthedocs.io/en/latest/index.html) -* [Sphinx](http://www.sphinx-doc.org/) -* [Getting started Screencast](https://www.youtube.com/watch?feature=player_embedded&v=oJsUvBQyHBs) +You can find more information about [reST](https://www.sphinx-doc.org/en/master/usage/restructuredtext/) markup and [Sphinx](https://github.com/sphinx-doc/sphinx) under the following links: +* [Read the Docs documentation](https://docs.readthedocs.io) +* [GitHub: The Sphinx documentation generator](https://github.com/sphinx-doc/sphinx) +* [Sphinx documentation](https://www.sphinx-doc.org/) +* [YouTube: Sphinx & Read the Docs by Mahdi Yusuf](https://www.youtube.com/watch?v=oJsUvBQyHBs) diff --git a/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs index 919262e55..6d43a168d 100644 --- a/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/IErrorsToHttpStatusCodeMapper.cs @@ -3,10 +3,15 @@ namespace Ocelot.Responder { /// - /// Map a list OceoltErrors to a single appropriate HTTP status code. + /// Defines mapping a list of Ocelot errors to a single appropriate HTTP status code. /// public interface IErrorsToHttpStatusCodeMapper { + /// + /// Maps a list of Ocelot to a single appropriate HTTP status code. + /// + /// The collection of errors. + /// An integer value with HTTP status code. int Map(List errors); } } From f5d94c995b0b3a332b3e723af199551dc1c8f393 Mon Sep 17 00:00:00 2001 From: raman-m Date: Fri, 27 Oct 2023 15:08:39 +0300 Subject: [PATCH 3/4] Prepare for the 20.0.1 release --- ReleaseNotes.md | 4 +-- build.cake | 88 ++++++++++++++++++++++++------------------------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 88ff76df0..af4e302ad 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,2 +1,2 @@ -## September 2023 (version {0}) aka [Polish Apple](https://www.google.com/search?q=Polish+Apple) release -> Codenamed as **[Polish Apple](https://www.google.com/search?q=Polish+Apple)** +## Documentation release {0} for [Polish Apple](https://www.google.com/search?q=Polish+Apple), v{1} +Special thanks to @ggnaegi! diff --git a/build.cake b/build.cake index 978664e2b..cdde8cb49 100644 --- a/build.cake +++ b/build.cake @@ -85,8 +85,8 @@ Task("Release") .IsDependentOn("Build") .IsDependentOn("CreateReleaseNotes") .IsDependentOn("CreateArtifacts") - .IsDependentOn("PublishGitHubRelease") - .IsDependentOn("PublishToNuget"); + .IsDependentOn("PublishGitHubRelease"); + //.IsDependentOn("PublishToNuget"); Task("Compile") .IsDependentOn("Clean") @@ -138,11 +138,6 @@ Task("CreateReleaseNotes") { Information($"Generating release notes at {releaseNotesFile}"); - var releaseVersion = versioning.NuGetVersion; - // Read main header from Git file, substitute version in header, and add content further... - var releaseHeader = string.Format(System.IO.File.ReadAllText("./ReleaseNotes.md"), releaseVersion); - var releaseNotes = new List { releaseHeader }; - // local helper function Func> GitHelper = (command) => { @@ -160,6 +155,11 @@ Task("CreateReleaseNotes") var lastRelease = lastReleaseTags.First(t => !t.StartsWith("net")); // skip 'net*-vX.Y.Z' tag and take 'major.minor.build' Information("Last release tag is " + lastRelease); + var releaseVersion = versioning.NuGetVersion; + // Read main header from Git file, substitute version in header, and add content further... + var releaseHeader = string.Format(System.IO.File.ReadAllText("./ReleaseNotes.md"), releaseVersion, lastRelease); + var releaseNotes = new List { releaseHeader }; + var shortlogSummary = GitHelper($"shortlog --no-merges --numbered --summary {lastRelease}..HEAD"); var re = new Regex(@"^[\s\t]*(?'commits'\d+)[\s\t]+(?'author'.*)$"); var summary = shortlogSummary @@ -296,9 +296,9 @@ Task("CreateReleaseNotes") } } } // END of Top 3 - releaseNotes.Add("### Honoring :medal_sports: aka Top Contributors :clap:"); - releaseNotes.AddRange(topContributors); - releaseNotes.Add(""); + //releaseNotes.Add("### Honoring :medal_sports: aka Top Contributors :clap:"); + //releaseNotes.AddRange(topContributors); + //releaseNotes.Add(""); releaseNotes.Add("### Starring :star: aka Release Influencers :bowtie:"); releaseNotes.AddRange(starring); releaseNotes.Add(""); @@ -409,17 +409,18 @@ Task("CreateArtifacts") { EnsureDirectoryExists(packagesDir); - CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir); + System.IO.File.AppendAllLines(artifactsFile, new[] { "ReleaseNotes.md" }); + CopyFiles("./ReleaseNotes.md", packagesDir); - var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg"); - - foreach(var projectFile in projectFiles) - { - System.IO.File.AppendAllLines( - artifactsFile, - new[] { projectFile.GetFilename().FullPath, "ReleaseNotes.md" } - ); - } + // CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir); + // var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg"); + // foreach(var projectFile in projectFiles) + // { + // System.IO.File.AppendAllLines( + // artifactsFile, + // new[] { projectFile.GetFilename().FullPath } + // ); + // } var artifacts = System.IO.File.ReadAllLines(artifactsFile) .Distinct(); @@ -427,8 +428,12 @@ Task("CreateArtifacts") foreach(var artifact in artifacts) { var codePackage = packagesDir + File(artifact); - - Information("Created package " + codePackage); + if (FileExists(codePackage)) + { + Information("Created package " + codePackage); + } else { + Information("Package does not exist: " + codePackage); + } } }); @@ -586,14 +591,12 @@ private void CreateGitHubRelease() var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); - using(var client = new System.Net.Http.HttpClient()) + using (var client = new System.Net.Http.HttpClient()) { - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", Convert.ToBase64String( - System.Text.ASCIIEncoding.ASCII.GetBytes( - $"{gitHubUsername}:{gitHubPassword}"))); - + client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{gitHubUsername}:{gitHubPassword}")) + ); client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); var result = client.PostAsync("https://api.github.com/repos/ThreeMammals/Ocelot/releases", content).Result; @@ -618,14 +621,12 @@ private void UploadFileToGitHubRelease(FilePath file) var content = new System.Net.Http.ByteArrayContent(data); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); - using(var client = new System.Net.Http.HttpClient()) + using (var client = new System.Net.Http.HttpClient()) { - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", Convert.ToBase64String( - System.Text.ASCIIEncoding.ASCII.GetBytes( - $"{gitHubUsername}:{gitHubPassword}"))); - + client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{gitHubUsername}:{gitHubPassword}")) + ); client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); var result = client.PostAsync($"https://uploads.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}/assets?name={file.GetFilename()}", content).Result; @@ -642,25 +643,22 @@ private void CompleteGitHubRelease() var request = new System.Net.Http.HttpRequestMessage(new System.Net.Http.HttpMethod("Patch"), $"https://api.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}"); request.Content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); - using(var client = new System.Net.Http.HttpClient()) + using (var client = new System.Net.Http.HttpClient()) { - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", Convert.ToBase64String( - System.Text.ASCIIEncoding.ASCII.GetBytes( - $"{gitHubUsername}:{gitHubPassword}"))); - + client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{gitHubUsername}:{gitHubPassword}")) + ); client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); var result = client.SendAsync(request).Result; - if(result.StatusCode != System.Net.HttpStatusCode.OK) + if (result.StatusCode != System.Net.HttpStatusCode.OK) { throw new Exception("CompleteGitHubRelease result.StatusCode = " + result.StatusCode); } } } - /// gets the resource from the specified url private async Task GetResourceAsync(string url) { From 028e257759b68532446ca905d2f1c331cb23a480 Mon Sep 17 00:00:00 2001 From: raman-m Date: Fri, 27 Oct 2023 15:15:35 +0300 Subject: [PATCH 4/4] Trigger new build... --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index cdde8cb49..ad510b0ac 100644 --- a/build.cake +++ b/build.cake @@ -86,7 +86,7 @@ Task("Release") .IsDependentOn("CreateReleaseNotes") .IsDependentOn("CreateArtifacts") .IsDependentOn("PublishGitHubRelease"); - //.IsDependentOn("PublishToNuget"); + // .IsDependentOn("PublishToNuget"); Task("Compile") .IsDependentOn("Clean")