惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tenable Blog
WordPress大学
WordPress大学
小众软件
小众软件
Y
Y Combinator Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
大猫的无限游戏
大猫的无限游戏
T
The Exploit Database - CXSecurity.com
Attack and Defense Labs
Attack and Defense Labs
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
量子位
有赞技术团队
有赞技术团队
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
S
Schneier on Security
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
Martin Fowler
Martin Fowler
Recent Announcements
Recent Announcements
Stack Overflow Blog
Stack Overflow Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
阮一峰的网络日志
阮一峰的网络日志
G
GRAHAM CLULEY
Spread Privacy
Spread Privacy
F
Full Disclosure
Scott Helme
Scott Helme
GbyAI
GbyAI
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
Cloudbric
Cloudbric
云风的 BLOG
云风的 BLOG
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
Hacker News - Newest:
Hacker News - Newest: "LLM"
Security Latest
Security Latest
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
MongoDB | Blog
MongoDB | Blog
The GitHub Blog
The GitHub Blog
The Register - Security
The Register - Security
L
Lohrmann on Cybersecurity
PCI Perspectives
PCI Perspectives
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Secure Thoughts
C
Check Point Blog

Martin Heinz's Blog

A Guide to Python's Weak References Using weakref Module Recent Docker BuildKit Features You're Missing Out On Modern Git Commands and Features You Should Be Using Everything You Can Do with Python's textwrap Module Monitoring Indoor Air Quality with Prometheus, Grafana and a CO2 Sensor Everything You Can Do with Python's bisect Module You Don't Need a Dedicated Cache Service - PostgreSQL as a Cache A Collection of Docker Images To Solve All Your Debugging Needs Weird Python "Features" That Might Catch You By Surprise Lessons Learned From Writing 100 Articles Debugging Crashes and Deadlocks in Python using PyStack Goodbye etcd, Hello PostgreSQL: Running Kubernetes with an SQL Database Remote Interactive Debugging of Python Applications Running in Kubernetes The Right Way to Run Shell Commands From Python Real Multithreading is Coming to Python - Learn How You Can Use It Now Python's Missing Batteries: Essential Libraries You're Missing Out On Kubernetes-Native Synthetic Monitoring with Kuberhealthy Make Your CLI Demos a Breeze with Zero Stress and Zero Mistakes Reduce - The Power of a Single Python Function Why I Will Never Use Alpine Linux Ever Again Cgroups - Deep Dive into Resource Management in Kubernetes Dictionary Dispatch Pattern in Python Boost Your Python Application Performance using Continuous Profiling Lazy Evaluation Using Recursive Python Generators Python Magic Methods You Haven't Heard About Getting Started with Mastodon API in Python Backup-and-Restore of Containers with Kubernetes Checkpointing API Getting Started with Google APIs in Python Python CLI Tricks That Don't Require Any Code Whatsoever All The Ways To Introspect Python Objects at Runtime What is Python's "self" Argument, Anyway? Python List Comprehensions Are More Powerful Than You Might Think You Should Be Using Python's Walrus Operator - Here's Why Recipes and Tricks for Effective Structural Pattern Matching in Python It's Time to Say Goodbye to These Obsolete Python Libraries Advanced Features of Kubernetes' Horizontal Pod Autoscaler Data and System Visualization Tools That Will Boost Your Productivity Stop Messing with Kubernetes Finalizers Automate All the Boring Kubernetes Operations with Python End-to-End Monitoring with Grafana Cloud with Minimal Effort Bitly | bit.ly/3JLmSgA Bitly | bit.ly/3uETfbi Bitly | bit.ly/3MI4Iz0 Bitly | bit.ly/3M30D82 Bitly | bit.ly/3oMJ6qR Bitly | bit.ly/3IRD7IK Bitly | bit.ly/3A3B69t Bitly | bit.ly/31lKCYA Bitly | bit.ly/30uviIM Bitly | bit.ly/3E1X2mw Bitly | bit.ly/3Dv7JxP Bitly | bit.ly/3GG1BEz Bitly | bit.ly/3lLavs4 Bitly | bit.ly/39TqP3m Bitly | bit.ly/3A5Mpx8 Bitly | bit.ly/3kGwPl4 Bitly | bit.ly/3iHtulU Bitly | bit.ly/3xGjtKS Bitly | bit.ly/3h8DZg0 Bitly | bit.ly/2RQn1dG Bitly | bit.ly/3p2B5wW The Easiest Way to Debug Kubernetes Workloads Bitly | bit.ly/2PHVudx Cloud Native CI/CD with Tekton - Building Custom Tasks Bitly | bit.ly/3dg3QR9 Bitly | bit.ly/3qHtSkZ Deep Dive into Docker Internals - Union Filesystem Bitly | bit.ly/3qlRAUN Bitly | bit.ly/3pCUJ26 Bitly | bit.ly/3ifZxYr Bitly | bit.ly/34ZhIMt Bitly | bit.ly/3qSO7h0 Bitly | bit.ly/3muGLOk Bitly | bit.ly/35xN79v Bitly | bit.ly/3mLGshK Bitly | bit.ly/2IvkGQl Bitly | bit.ly/2Sk1KFK Bitly | bit.ly/3iCNIL6 Bitly | bit.ly/3beQPpy Saving Your Linux Machine from Certain Death New Features in Python 3.9 You Should Know About Deploy Any Python Project to Kubernetes Analyzing Docker Image Security Recursive SQL Queries with PostgreSQL Automating Every Aspect of Your Python Project Tour of Python Itertools Implementing 2D Physics in Javascript Ultimate Setup for Your Next Python Project Making Python Programs Blazingly Fast Security and Cryptography Mistakes You Are Probably Doing All The Time Going Serverless with OpenFaaS and Golang - Building Optimized Templates Going Serverless with OpenFaaS and Golang - The Ultimate Setup and Workflow Building RESTful APIs in Golang Pytest Features, That You Need in Your (Testing) Life Setting up GitHub Package Registry with Docker and Golang Ultimate Setup for Your Next Golang Project Python Tips and Trick, You Haven't Already Seen, Part 2. Tricks for Postgres and Docker that will make your life easier Getting The Most Out of Reading Books - Reading The "Professional Way" Python Tips and Trick, You Haven't Already Seen
Setting Up Swagger Docs for Golang API
Martin · 2019-11-01 · via Martin Heinz's Blog

In the previous post - Building RESTful APIs in Golang - we created RESTful API in Golang. Now, that we have our project ready to be used, we should show our users how to do so, otherwise - if they can't test it and view its features - they won't even touch it.

Without meaningful documentation of our APIs as well as an ability to test its endpoints, users won't even bother trying to use it. Solution to that is writing documentation. However, writing it may take lots of time, which could otherwise used to develop more cool features for our applications... So, what do we do? - we generate Swagger docs!

Libraries

Let's start with libraries needed to create the Swagger docs. I said libraries, but really just need one - swag - which is Golang library that converts code annotations (comments) to Swagger Documentation 2.0. Aside from swag you will need a middleware/wrapper library for your web framework. In swag docs there are links to libraries for supported frameworks, which include both the simplest option of net/http which a lot of people like to use as well as GIN, which I use and which I will show here. Even though you might be using different web framework, the annotations are gonna be the same, so you can learn something here anyway.

It's also worth to mention, that there is alternative Golang Swagger library - go-swagger which seems to be more popular and also quite a bit more powerful. I, personally, however prefer to swaggo/swag because of its simplicity. If you need more control over what gets generated you might want switch to go-swagger.

Docstrings

Now, for the annotations/comments/docstring or whatever you want to call it. It's really just bunch of comments before specific API function, which is used to generate the Swagger docs.

Before we get to describing individual API endpoints, we need to first write general description for our whole project. This part of annotations lives in your main package, right before the main function:

Note: All the examples below come from my repository here, where you can find runnable application with the Swagger UI/Docs included.


import (
    ...
	swaggerFiles "github.com/swaggo/files"
	"github.com/swaggo/gin-swagger"

	_ "github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/docs"
)

// @title Blueprint Swagger API
// @version 1.0
// @description Swagger API for Golang Project Blueprint.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.email martin7.heinz@gmail.com

// @license.name MIT
// @license.url https://github.com/MartinHeinz/go-project-blueprint/blob/master/LICENSE

// @BasePath /api/v1
func main() {
    ...
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    ...
}

Above you can see example of General API Info, which includes things like name, version, license, base URL etc. There are few more fields that you can include and they are listed here with some examples.

Apart from the annotations, we also need to import necessary libraries including blank import of our docs package that we have to generate (more on that later). One more thing we need to do, is to actually mount the Swagger UI at some endpoint, here we use "/swagger/*any.

This is the part of UI, which the annotations above would produce:

Now for the important part - annotations for API functions. These annotations precede each function that is wired in main to serve some endpoint, so when we serve endpoint like v1.GET("/users/:id", apis.GetUser), we need to annotate it like this:


// GetUser godoc
// @Summary Retrieves user based on given ID
// @Produce json
// @Param id path integer true "User ID"
// @Success 200 {object} models.User
// @Router /users/{id} [get]
func GetUser(c *gin.Context) {
    ...
}

Most of these are pretty self-explanatory and this is really minimal set of annotations that you should include. One thing I want to highlight though, is the models.User being returned on success - this is a model of database table that lives in models package. By referencing it like this, we cause it to appear in Swagger UI in the models section:

And this is a section that we get for our endpoint:

Generate!

Finally, it's time to generate the docs! All you need is one command - swag init, this command needs to be ran from directory where main is, so for the blueprint repository I made, it would be .../cmd/blueprint/. This command will create package called docs, which includes both JSON and YAML version of our docs.

Even though this package is generated, I prefer to store it in GitHub, as it is imported in the main package and therefore it's necessary for application to run. If you want to avoid pushing this generated code to GitHub, you could for example write a Makefile target, that would re-generate the Swagger docs on-the-fly before application is built and ran. If you however, decide to push it GitHub, you might want to run the docs through go fmt as it's not necessarily formatted "as it should be".

Authentication

At this point, we could just run the application, look at our nice new Swagger UI and call it a day. One thing that is missing though, is authentication for the API. If you leave Swagger UI unauthenticated, then anybody can hit any endpoint they want, which might be very undesirable, if - for example - your data could be damaged by users. Even worse, you might expose sensitive information from your database to the whole internet. I think those are enough reasons to setup some simple authentication for our API and therefore, also Swagger UI, so how do we do it?

First of all, we need to actually implement the authentication. Here, is case of GIN, we create a very simple authentication middleware, which we attach to router group:


func auth() gin.HandlerFunc {  // Middleware function
	return func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")  // Retrieve the header
		if len(authHeader) == 0 {  // Check if it's set
			httputil.NewError(c, http.StatusUnauthorized, errors.New("Authorization is required Header"))
			c.Abort()
		}
		if authHeader != config.Config.ApiKey {  // Check if the API Key is correct
			httputil.NewError(c, http.StatusUnauthorized, fmt.Errorf("this user isn't authorized to this operation: api_key=%s", authHeader))
			c.Abort()
		}
		c.Next()
	}
}

By attaching the middleware to specific group(s) we can control what is and what is not authenticated, which is important because we - for example - don't want Swagger UI itself to be authenticated.

Note: I've omitted some of the code, to make the examples readable and easy to follow. For full code, see the rest-api branch in repository here.

One more thing that we need to change in main module is annotations - more specifically, we need to add the securityDefinitions annotation:


// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
func main() {
    ...
}

This annotation - as you can already guess - adds API key authentication through Authorization header to the Swagger UI. Apart from API key authentication you could also choose to use basic authentication (securitydefinitions.basic) using username and password or some version of OAuth2 (securitydefinitions.oauth2), all options are shown in documentation here. I personally like to use API key as it is simple and the most convenient option in my opinion.

For Swagger to recognize, that some endpoint is authenticated, we also need to add security annotation to said API function:


// @Security ApiKeyAuth
func GetUser(c *gin.Context) {
    ...
}

This was the last step and now (after regenerating Swagger docs) we can finally run our application:


foo@bar:~$ docker run -p 1234:1234 \
               -e BLUEPRINT_DSN="postgres://<USER>:<PASSWORD>@<DOCKER_SERVICE/URL>:<PORT>/<DB>?sslmode=disable" \
               -e BLUEPRINT_API_KEY="api_key" \
               docker.pkg.github.com/martinheinz/go-project-blueprint/blueprint:latest

And you should see something like this in GIN logs:


[GIN-debug] GET    /swagger/*any             --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (3 handlers)  # <- Swagger is ready!
[GIN-debug] GET    /api/v1/users/:id         --> github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/apis.GetUser (4 handlers)

We can now open the Swagger UI at http://localhost:1234/swagger/index.html and test our documentation!

Note: If the command above doesn't make much sense to you, please consider also reading through previous post here.

So, just to recap - the security definition in main package gives us following modal:

Then, if we enter incorrect API key ("wrong_api_key") we get 401 response code:

And if we use correct API key we get back 200 with the data requested:

It's important to also mention, that sending authorization headers in plaintext like we have done here is not secure whatsoever and defeats the whole purpose of authenticating the API, so in real application you should definitely use HTTPS.

Conclusion

After reading this article, I hope you now know, how to go about setting up Swagger docs for your API and I also hope that you will actually write some docs for your next project as it's pretty simple and there's great value in good API documentation for both you (manual testing) and users of your application. You can checkout full code in my repository's rest-api branch here and if you have questions or improvements, feel free to reach out to me or create issue/pull request in the repository. 🙂