README

gadget gets the browser and OS from the User-Agent header.

ua := gadget.Parse(`Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0`)

fmt.Println(ua.String())  // "Firefox 73 on Windows 10"
fmt.Println(ua.Browser()) // "Firefox 73"
fmt.Println(ua.OS())      // "Windows 10"

// Or for more detailed information:
fmt.Println(ua.BrowserName)    // "Firefox"
fmt.Println(ua.BrowserVersion) // "73"
fmt.Println(ua.OSName)         // "Windows"
fmt.Println(ua.OSVersion)      // "10"

// Helper to shorten the UA string while remaining readable:
uaHeader := `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4029.0 Safari/537.36`
short := gadget.Shorten(uaHeader)
fmt.Println(short)                               // ~Z (~W NT 10.0; Win64; x64) ~a537.36 ~G ~c81.0.4029.0 ~s537.36
fmt.Println(gadget.Unshorten(short) == uaHeader) // true

Some design principles:

  • Just get a "common sense" name and version. Stuff like "Chrome 80.0.3987.87" or "Linux x86_64" is rarely useful; just get "Chrome 80" and "Linux".

  • This mostly identifies the browser engine rather than the actual browser. It doesn't really matter if someone is using Opera 80, Edge 80, Samsung browser, or Chrome 80: they all exhibit the same behaviour, so just report it as "Chrome 80".

  • Don't try to guess if we're dealing with a bot. Use zgo.at/isbot if you want to do that. This also doesn't go out of its way to parse the bot name; it's mostly intended for actual browsers people use.

  • It also doesn't try to determine if this is a "mobile" browser; what does "mobile" even mean? Why should a 12" tablet be mobile and my 12" laptop not? It's usually better (and more reliable!) to rely on the screen width and/or use JS to determine if a client supports touch events and is "mobile".

  • Don't go out of our way to get rather old User-Agents correct; some libraries have test cases for things like "Netscape on Windows ME", which doesn't strike me as very useful in 2020.

  • If we don't know, then we don't know. Don't return useless values like "AppleWebKit 605.1.15" if there is no other information.

While this won't cover 100% of the use cases, it makes it fast and easy to use for other use cases. Specifically, it was designed to show browser and OS stats in GoatCounter, where you typically don't really care if someone is using Opera or Chrome, but just want to know which browser engines your customers are using and you need to support.


Most other libraries give far too detailed information to be useful, and some are lacking in accuracy too.

There's a small tail-end of browsers that aren't recognized correctly; only 17 out of those 371,021 are parsed to "junk data" (or 46 requests in total, out of 9.4 million). This is mostly due to people sending junk data such as misspelling Mozilla as "Mozzila", and not too much can be done about that.

Getting it right 99.999995% of the time is good enough for me :-) It's not like the User-Agent is reliable anyway (Ever heard of "Chrome 66.6" or "Chrome 999999"?), so this is fine.

Simple comparison benchmark (from testlib.gox):

Library     Total (577×10)  Per op
gadget      0.0164s         2.846µs
uasurfer    0.0361s         6.255µs
useragent   0.0310s         5.379µs
user_agent  0.0275s         4.764µs
uaparser    11.7110s        2.029633ms
Expand ▾ Collapse ▴

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Shorten

func Shorten(ua string) string

Shorten a User-Agent string by replacing common strings with small tokens.

Use Unshorten() to reverse it.

Example:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
~Z (~W NT 10.0; Win64; x64) ~a537.36 ~G ~c80.0.3987.132 ~s537.36

The goal is not to produce the shortest output, but to provide a reasonably short output while maintaining readability.

Inspired by: https://github.com/icza/gox/blob/master/netx/httpx/httpx.go

func Unshorten

func Unshorten(short string) string

Unshorten reverses Shorten().

Types

type UserAgent

type UserAgent struct {
	BrowserName    string
	BrowserVersion string
	OSName         string
	OSVersion      string
}

func Parse

func Parse(uaHeader string) UserAgent

Parse a User-Agent header.

Example

Code:

package main

import (
	"fmt"
	"zgo.at/gadget"
)

func main() {
	ua := gadget.Parse(`Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0`)

	fmt.Println(ua.String())  // "Firefox 73 on Windows 10"
	fmt.Println(ua.Browser()) // "Firefox 73"
	fmt.Println(ua.OS())      // "Windows 10"

	// Or for more detailed information:
	fmt.Println(ua.BrowserName)    // "Firefox"
	fmt.Println(ua.BrowserVersion) // "73"
	fmt.Println(ua.OSName)         // "Windows"
	fmt.Println(ua.OSVersion)      // "10"

	// Helper to shorten the UA string while remaining readable:
	uaHeader := `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4029.0 Safari/537.36`
	short := gadget.Shorten(uaHeader)
	fmt.Println(short)                               // ~Z (~W NT 10.0; Win64; x64) ~a537.36 ~G ~c81.0.4029.0 ~s537.36
	fmt.Println(gadget.Unshorten(short) == uaHeader) // true

}
Firefox 73 on Windows 10
Firefox 73
Windows 10
Firefox
73
Windows
10
~Z (~W NT 10.0; Win64; x64) ~a537.36 ~G ~c81.0.4029.0 ~s537.36
true

func (UserAgent) Browser

func (u UserAgent) Browser() string

Browser gets the full browser, including the version (if any).

func (UserAgent) OS

func (u UserAgent) OS() string

OS gets the full operating system, including the version (if any).

func (UserAgent) String

func (u UserAgent) String() string

String shows the full Browser and OS name as "<browser> on <os>". If either one is blank the "on" will be omitted.

Source Files