go
56 lines · 7 steps
Parsing user-agent strings in Go
A small package extracts browser, version, and OS from a raw user-agent string using ordered regex and substring checks.
Explained by
highlit
1package useragent
2
3import (
4 "regexp"
5 "strings"
6)
7
8type Parsed struct {
9 Browser string
10 BrowserVersion string
11 OS string
12 Mobile bool
13}
14
15var (
16 edgeRe = regexp.MustCompile(`Edg(?:e|A|iOS)?/([\d.]+)`)
17 operaRe = regexp.MustCompile(`OPR/([\d.]+)`)
18 chromeRe = regexp.MustCompile(`Chrome/([\d.]+)`)
19 firefoxRe = regexp.MustCompile(`Firefox/([\d.]+)`)
20 safariRe = regexp.MustCompile(`Version/([\d.]+).*Safari`)
21)
22
23func Parse(ua string) Parsed {
24 p := Parsed{Browser: "Unknown", OS: "Unknown"}
25 p.Mobile = strings.Contains(ua, "Mobi") || strings.Contains(ua, "Android")
26
27 switch {
28 case edgeRe.MatchString(ua):
29 p.Browser, p.BrowserVersion = "Edge", edgeRe.FindStringSubmatch(ua)[1]
30 case operaRe.MatchString(ua):
31 p.Browser, p.BrowserVersion = "Opera", operaRe.FindStringSubmatch(ua)[1]
32 case chromeRe.MatchString(ua):
33 p.Browser, p.BrowserVersion = "Chrome", chromeRe.FindStringSubmatch(ua)[1]
34 case firefoxRe.MatchString(ua):
35 p.Browser, p.BrowserVersion = "Firefox", firefoxRe.FindStringSubmatch(ua)[1]
36 case safariRe.MatchString(ua):
37 p.Browser, p.BrowserVersion = "Safari", safariRe.FindStringSubmatch(ua)[1]
38 }
39
40 switch {
41 case strings.Contains(ua, "Windows NT 10.0"):
42 p.OS = "Windows 10"
43 case strings.Contains(ua, "Windows NT"):
44 p.OS = "Windows"
45 case strings.Contains(ua, "iPhone"), strings.Contains(ua, "iPad"):
46 p.OS = "iOS"
47 case strings.Contains(ua, "Mac OS X"):
48 p.OS = "macOS"
49 case strings.Contains(ua, "Android"):
50 p.OS = "Android"
51 case strings.Contains(ua, "Linux"):
52 p.OS = "Linux"
53 }
54
55 return p
56}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Ordering matters when patterns overlap — check the most specific signature before the generic one it contains.
- 2Precompiling regexes once at package scope avoids recompiling on every call.
- 3A safe default plus a fall-through switch keeps unknown inputs from producing empty or invalid results.
Related explainers
php
<?php class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/parsing-user-agent-strings-in-go-explained-go-c0d2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.