package fasthttpimport ()// AcquireURI returns an empty URI instance from the pool.//// Release the URI with ReleaseURI after the URI is no longer needed.// This allows reducing GC load.func () *URI {returnuriPool.Get().(*URI)}// ReleaseURI releases the URI acquired via AcquireURI.//// The released URI mustn't be used after releasing it, otherwise data races// may occur.func ( *URI) { .Reset()uriPool.Put()}var uriPool = &sync.Pool{New: func() interface{} {return &URI{} },}// URI represents URI :) .//// It is forbidden copying URI instances. Create new instance and use CopyTo// instead.//// URI instance MUST NOT be used from concurrently running goroutines.typeURIstruct { noCopy noCopy pathOriginal []byte scheme []byte path []byte queryString []byte hash []byte host []byte queryArgs Args parsedQueryArgs bool// Path values are sent as-is without normalization // // Disabled path normalization may be useful for proxying incoming requests // to servers that are expecting paths to be forwarded as-is. // // By default path values are normalized, i.e. // extra slashes are removed, special characters are encoded. DisablePathNormalizing bool fullURI []byte requestURI []byte username []byte password []byte}// CopyTo copies uri contents to dst.func ( *URI) ( *URI) { .Reset() .pathOriginal = append(.pathOriginal, .pathOriginal...) .scheme = append(.scheme, .scheme...) .path = append(.path, .path...) .queryString = append(.queryString, .queryString...) .hash = append(.hash, .hash...) .host = append(.host, .host...) .username = append(.username, .username...) .password = append(.password, .password...) .queryArgs.CopyTo(&.queryArgs) .parsedQueryArgs = .parsedQueryArgs .DisablePathNormalizing = .DisablePathNormalizing// fullURI and requestURI shouldn't be copied, since they are created // from scratch on each FullURI() and RequestURI() call.}// Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte {return .hash}// SetHash sets URI hash.func ( *URI) ( string) { .hash = append(.hash[:0], ...)}// SetHashBytes sets URI hash.func ( *URI) ( []byte) { .hash = append(.hash[:0], ...)}// Username returns URI username//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte {return .username}// SetUsername sets URI username.func ( *URI) ( string) { .username = append(.username[:0], ...)}// SetUsernameBytes sets URI username.func ( *URI) ( []byte) { .username = append(.username[:0], ...)}// Password returns URI password//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte {return .password}// SetPassword sets URI password.func ( *URI) ( string) { .password = append(.password[:0], ...)}// SetPasswordBytes sets URI password.func ( *URI) ( []byte) { .password = append(.password[:0], ...)}// QueryString returns URI query string,// i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte {return .queryString}// SetQueryString sets URI query string.func ( *URI) ( string) { .queryString = append(.queryString[:0], ...) .parsedQueryArgs = false}// SetQueryStringBytes sets URI query string.func ( *URI) ( []byte) { .queryString = append(.queryString[:0], ...) .parsedQueryArgs = false}// Path returns URI path, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .//// The returned path is always urldecoded and normalized,// i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte { := .pathiflen() == 0 { = strSlash }return}// SetPath sets URI path.func ( *URI) ( string) { .pathOriginal = append(.pathOriginal[:0], ...) .path = normalizePath(.path, .pathOriginal)}// SetPathBytes sets URI path.func ( *URI) ( []byte) { .pathOriginal = append(.pathOriginal[:0], ...) .path = normalizePath(.path, .pathOriginal)}// PathOriginal returns the original path from requestURI passed to URI.Parse().//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte {return .pathOriginal}// Scheme returns URI scheme, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .//// Returned scheme is always lowercased.//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte { := .schemeiflen() == 0 { = strHTTP }return}// SetScheme sets URI scheme, i.e. http, https, ftp, etc.func ( *URI) ( string) { .scheme = append(.scheme[:0], ...)lowercaseBytes(.scheme)}// SetSchemeBytes sets URI scheme, i.e. http, https, ftp, etc.func ( *URI) ( []byte) { .scheme = append(.scheme[:0], ...)lowercaseBytes(.scheme)}func ( *URI) () bool {returnbytes.Equal(.scheme, strHTTPS)}func ( *URI) () bool {returnlen(.scheme) == 0 || bytes.Equal(.scheme, strHTTP)}// Reset clears uri.func ( *URI) () { .pathOriginal = .pathOriginal[:0] .scheme = .scheme[:0] .path = .path[:0] .queryString = .queryString[:0] .hash = .hash[:0] .username = .username[:0] .password = .password[:0] .host = .host[:0] .queryArgs.Reset() .parsedQueryArgs = false .DisablePathNormalizing = false// There is no need in u.fullURI = u.fullURI[:0], since full uri // is calculated on each call to FullURI().// There is no need in u.requestURI = u.requestURI[:0], since requestURI // is calculated on each call to RequestURI().}// Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .//// Host is always lowercased.//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte {return .host}// SetHost sets host for the uri.func ( *URI) ( string) { .host = append(.host[:0], ...)lowercaseBytes(.host)}// SetHostBytes sets host for the uri.func ( *URI) ( []byte) { .host = append(.host[:0], ...)lowercaseBytes(.host)}varErrorInvalidURI = errors.New("invalid uri")// Parse initializes URI from the given host and uri.//// host may be nil. In this case uri must contain fully qualified uri,// i.e. with scheme and host. http is assumed if scheme is omitted.//// uri may contain e.g. RequestURI without scheme and host if host is non-empty.func ( *URI) (, []byte) error {return .parse(, , false)}func ( *URI) (, []byte, bool) error { .Reset()ifstringContainsCTLByte() {returnErrorInvalidURI }iflen() == 0 || bytes.Contains(, strColonSlashSlash) { , , := splitHostURI(, ) .SetSchemeBytes() = = }if { .SetSchemeBytes(strHTTPS) }if := bytes.IndexByte(, '@'); >= 0 { := [:] = [+1:]if := bytes.IndexByte(, ':'); >= 0 { .username = append(.username[:0], [:]...) .password = append(.password[:0], [+1:]...) } else { .username = append(.username[:0], ...) .password = .password[:0] } } .host = append(.host, ...)if , := parseHost(.host); != nil {return } else { .host = }lowercaseBytes(.host) := := bytes.IndexByte(, '?') := bytes.IndexByte(, '#')// Ignore query in fragment partif >= 0 && > { = -1 }if < 0 && < 0 { .pathOriginal = append(.pathOriginal, ...) .path = normalizePath(.path, .pathOriginal)returnnil }if >= 0 {// Path is everything up to the start of the query .pathOriginal = append(.pathOriginal, [:]...) .path = normalizePath(.path, .pathOriginal)if < 0 { .queryString = append(.queryString, [+1:]...) } else { .queryString = append(.queryString, [+1:]...) .hash = append(.hash, [+1:]...) }returnnil }// fragmentIndex >= 0 && queryIndex < 0 // Path is up to the start of fragment .pathOriginal = append(.pathOriginal, [:]...) .path = normalizePath(.path, .pathOriginal) .hash = append(.hash, [+1:]...)returnnil}// parseHost parses host as an authority without user// information. That is, as host[:port].//// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L619//// The host is parsed and unescaped in place overwriting the contents of the host parameter.func parseHost( []byte) ([]byte, error) {iflen() > 0 && [0] == '[' {// Parse an IP-Literal in RFC 3986 and RFC 6874. // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80". := bytes.LastIndexByte(, ']')if < 0 {returnnil, errors.New("missing ']' in host") } := [+1:]if !validOptionalPort() {returnnil, fmt.Errorf("invalid port %q after host", ) }// RFC 6874 defines that %25 (%-encoded percent) introduces // the zone identifier, and the zone identifier can use basically // any %-encoding it likes. That's different from the host, which // can only %-encode non-ASCII bytes. // We do impose some restrictions on the zone, to avoid stupidity // like newlines. := bytes.Index([:], []byte("%25"))if >= 0 { , := unescape([:], encodeHost)if != nil {returnnil, } , := unescape([:], encodeZone)if != nil {returnnil, } , := unescape([:], encodeHost)if != nil {returnnil, }returnappend(, append(, ...)...), nil } } elseif := bytes.LastIndexByte(, ':'); != -1 { := [:]if !validOptionalPort() {returnnil, fmt.Errorf("invalid port %q after host", ) } }varerrorif , = unescape(, encodeHost); != nil {returnnil, }return , nil}type encoding intconst ( encodeHost encoding = 1 + iota encodeZone)typeEscapeErrorstringfunc ( EscapeError) () string {return"invalid URL escape " + strconv.Quote(string())}typeInvalidHostErrorstringfunc ( InvalidHostError) () string {return"invalid character " + strconv.Quote(string()) + " in host name"}// unescape unescapes a string; the mode specifies// which section of the URL string is being unescaped.//// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L199//// Unescapes in place overwriting the contents of s and returning it.func unescape( []byte, encoding) ([]byte, error) {// Count %, check that they're well-formed. := 0for := 0; < len(); {switch [] {case'%': ++if +2 >= len() || !ishex([+1]) || !ishex([+2]) { = [:]iflen() > 3 { = [:3] }returnnil, EscapeError() }// Per https://tools.ietf.org/html/rfc3986#page-21 // in the host component %-encoding can only be used // for non-ASCII bytes. // But https://tools.ietf.org/html/rfc6874#section-2 // introduces %25 being allowed to escape a percent sign // in IPv6 scoped-address literals. Yay.if == encodeHost && unhex([+1]) < 8 && !bytes.Equal([:+3], []byte("%25")) {returnnil, EscapeError([ : +3]) }if == encodeZone {// RFC 6874 says basically "anything goes" for zone identifiers // and that even non-ASCII can be redundantly escaped, // but it seems prudent to restrict %-escaped bytes here to those // that are valid host name bytes in their unescaped form. // That is, you can use escaping in the zone identifier but not // to introduce bytes you couldn't just write directly. // But Windows puts spaces here! Yay. := unhex([+1])<<4 | unhex([+2])if !bytes.Equal([:+3], []byte("%25")) && != ' ' && shouldEscape(, encodeHost) {returnnil, EscapeError([ : +3]) } } += 3default:if ( == encodeHost || == encodeZone) && [] < 0x80 && shouldEscape([], ) {returnnil, InvalidHostError([ : +1]) } ++ } }if == 0 {return , nil } := [:0]for := 0; < len(); ++ {switch [] {case'%': = append(, unhex([+1])<<4|unhex([+2])) += 2default: = append(, []) } }return , nil}// Return true if the specified character should be escaped when// appearing in a URL string, according to RFC 3986.//// Please be informed that for now shouldEscape does not check all// reserved characters correctly. See https://github.com/golang/go/issues/5684.//// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100func shouldEscape( byte, encoding) bool {// §2.3 Unreserved characters (alphanum)if'a' <= && <= 'z' || 'A' <= && <= 'Z' || '0' <= && <= '9' {returnfalse }if == encodeHost || == encodeZone {// §3.2.2 Host allows // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" // as part of reg-name. // We add : because we include :port as part of host. // We add [ ] because we include [ipv6]:port as part of host. // We add < > because they're the only characters left that // we could possibly allow, and Parse will reject them if we // escape them (because hosts can't use %-encoding for // ASCII bytes).switch {case'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':returnfalse } }if == '-' || == '_' || == '.' || == '~' { // §2.3 Unreserved characters (mark)returnfalse }// Everything else must be escaped.returntrue}func ishex( byte) bool {return ('0' <= && <= '9') || ('a' <= && <= 'f') || ('A' <= && <= 'F')}func unhex( byte) byte {switch {case'0' <= && <= '9':return - '0'case'a' <= && <= 'f':return - 'a' + 10case'A' <= && <= 'F':return - 'A' + 10 }return0}// validOptionalPort reports whether port is either an empty string// or matches /^:\d*$/func validOptionalPort( []byte) bool {iflen() == 0 {returntrue }if [0] != ':' {returnfalse }for , := range [1:] {if < '0' || > '9' {returnfalse } }returntrue}func normalizePath(, []byte) []byte { = [:0] = addLeadingSlash(, ) = decodeArgAppendNoPlus(, )// remove duplicate slashes := := len()for { := bytes.Index(, strSlashSlash)if < 0 {break } = [:]copy(, [1:]) = [:len()-1] -- } = [:]// remove /./ parts = for { := bytes.Index(, strSlashDotSlash)if < 0 {break } := + len(strSlashDotSlash) - 1copy([:], [:]) = [:len()-+] }// remove /foo/../ partsfor { := bytes.Index(, strSlashDotDotSlash)if < 0 {break } := bytes.LastIndexByte([:], '/')if < 0 { = 0 } += len(strSlashDotDotSlash) - 1copy([:], [:]) = [:len()-+] }// remove trailing /foo/.. := bytes.LastIndex(, strSlashDotDot)if >= 0 && +len(strSlashDotDot) == len() { := bytes.LastIndexByte([:], '/')if < 0 {returnappend([:0], strSlash...) } = [:+1] }iffilepath.Separator == '\\' {// remove \.\ partsfor { := bytes.Index(, strBackSlashDotBackSlash)if < 0 {break } := + len(strSlashDotSlash) - 1copy([:], [:]) = [:len()-+] }// remove /foo/..\ partsfor { := bytes.Index(, strSlashDotDotBackSlash)if < 0 {break } := bytes.LastIndexByte([:], '/')if < 0 { = 0 } ++ += len(strSlashDotDotBackSlash)copy([:], [:]) = [:len()-+] }// remove /foo\..\ partsfor { := bytes.Index(, strBackSlashDotDotBackSlash)if < 0 {break } := bytes.LastIndexByte([:], '/')if < 0 { = 0 } += len(strBackSlashDotDotBackSlash) - 1copy([:], [:]) = [:len()-+] }// remove trailing \foo\.. := bytes.LastIndex(, strBackSlashDotDot)if >= 0 && +len(strSlashDotDot) == len() { := bytes.LastIndexByte([:], '/')if < 0 {returnappend([:0], strSlash...) } = [:+1] } }return}// RequestURI returns RequestURI - i.e. URI without Scheme and Host.func ( *URI) () []byte {var []byteif .DisablePathNormalizing { = .requestURI[:0] = append(, .PathOriginal()...) } else { = appendQuotedPath(.requestURI[:0], .Path()) }if .parsedQueryArgs && .queryArgs.Len() > 0 { = append(, '?') = .queryArgs.AppendBytes() } elseiflen(.queryString) > 0 { = append(, '?') = append(, .queryString...) } .requestURI = return .requestURI}// LastPathSegment returns the last part of uri path after '/'.//// Examples://// - For /foo/bar/baz.html path returns baz.html.// - For /foo/bar/ returns empty byte slice.// - For /foobar.js returns foobar.js.//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte { := .Path() := bytes.LastIndexByte(, '/')if < 0 {return }return [+1:]}// Update updates uri.//// The following newURI types are accepted://// - Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original// uri is replaced by newURI.// - Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case// the original scheme is preserved.// - Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part// of the original uri is replaced.// - Relative path, i.e. xx?yy=abc . In this case the original RequestURI// is updated according to the new relative path.func ( *URI) ( string) { .UpdateBytes(s2b())}// UpdateBytes updates uri.//// The following newURI types are accepted://// - Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original// uri is replaced by newURI.// - Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case// the original scheme is preserved.// - Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part// of the original uri is replaced.// - Relative path, i.e. xx?yy=abc . In this case the original RequestURI// is updated according to the new relative path.func ( *URI) ( []byte) { .requestURI = .updateBytes(, .requestURI)}func ( *URI) (, []byte) []byte {iflen() == 0 {return } := bytes.Index(, strSlashSlash)if >= 0 {// absolute urivar [32]byte := [:0]iflen(.scheme) > 0 { = append([]byte(nil), .scheme...) }if := .Parse(nil, ); != nil {returnnil }iflen() > 0 && len(.scheme) == 0 { .scheme = append(.scheme[:0], ...) }return }if [0] == '/' {// uri without host = .appendSchemeHost([:0]) = append(, ...)if := .Parse(nil, ); != nil {returnnil }return }// relative pathswitch [0] {case'?':// query string only update .SetQueryStringBytes([1:])returnappend([:0], .FullURI()...)case'#':// update only hash .SetHashBytes([1:])returnappend([:0], .FullURI()...)default:// update the last path part after the slash := .Path() = bytes.LastIndexByte(, '/')if < 0 {panic(fmt.Sprintf("BUG: path must contain at least one slash: %q %q", .Path(), )) } = .appendSchemeHost([:0]) = appendQuotedPath(, [:+1]) = append(, ...)if := .Parse(nil, ); != nil {returnnil }return }}// FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.//// The returned bytes are valid until the next URI method call.func ( *URI) () []byte { .fullURI = .AppendBytes(.fullURI[:0])return .fullURI}// AppendBytes appends full uri to dst and returns the extended dst.func ( *URI) ( []byte) []byte { = .appendSchemeHost() = append(, .RequestURI()...)iflen(.hash) > 0 { = append(, '#') = append(, .hash...) }return}func ( *URI) ( []byte) []byte { = append(, .Scheme()...) = append(, strColonSlashSlash...)returnappend(, .Host()...)}// WriteTo writes full uri to w.//// WriteTo implements io.WriterTo interface.func ( *URI) ( io.Writer) (int64, error) { , := .Write(.FullURI())returnint64(), }// String returns full uri.func ( *URI) () string {returnstring(.FullURI())}func splitHostURI(, []byte) ([]byte, []byte, []byte) { := bytes.Index(, strSlashSlash)if < 0 {returnstrHTTP, , } := [:]ifbytes.IndexByte(, '/') >= 0 {returnstrHTTP, , }iflen() > 0 && [len()-1] == ':' { = [:len()-1] } += len(strSlashSlash) = [:] = bytes.IndexByte(, '/') := bytes.IndexByte(, '?')if >= 0 && < {// A hack for urls like foobar.com?a=b/xyz = } elseif < 0 {// A hack for bogus urls like foobar.com?a=b without // slash after host.if >= 0 {return , [:], [:] }return , , strSlash }return , [:], [:]}// QueryArgs returns query args.//// The returned args are valid until the next URI method call.func ( *URI) () *Args { .parseQueryArgs()return &.queryArgs}func ( *URI) () {if .parsedQueryArgs {return } .queryArgs.ParseBytes(.queryString) .parsedQueryArgs = true}// stringContainsCTLByte reports whether s contains any ASCII control character.func stringContainsCTLByte( []byte) bool {for := 0; < len(); ++ { := []if < ' ' || == 0x7f {returntrue } }returnfalse}
The pages are generated with Goldsv0.6.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.