[インデックス 1605] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
パッケージにおけるHTTPサーバーの実装を大幅に強化し、より堅牢でGoらしい設計へと進化させることを目的としています。具体的には、HTTPサーバーの機能拡張、命名規則の統一(エクスポートされる識別子の大文字化)、およびHTTPプロトコル処理の改善が含まれています。
コミット
commit e73acc1b35f3490f3d800b4bf49da79630e808fc
Author: Russ Cox <rsc@golang.org>
Date: Mon Feb 2 18:01:32 2009 -0800
flesh out http server.
convert to uppercase names.
R=r
DELTA=613 (460 added, 61 deleted, 92 changed)
OCL=24139
CL=24145
---
src/lib/http/conn.go | 53 ------
src/lib/http/request.go | 123 +++++++++-----\n src/lib/http/server.go | 424 +++++++++++++++++++++++++++++++++++++++++++++---\n src/lib/http/triv.go | 50 ++++--\n src/lib/http/url.go | 42 ++---\n 5 files changed, 545 insertions(+), 147 deletions(-)
diff --git a/src/lib/http/conn.go b/src/lib/http/conn.go
deleted file mode 100644
index 909863ef58..0000000000
--- a/src/lib/http/conn.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package http
-
-import (
- "io";
- "bufio";
- "http";
- "os"
-)
-
-// Active HTTP connection (server side).
-type Conn struct {
- rwc io.ReadWriteClose;
- br *bufio.BufRead;
- bw *bufio.BufWrite;
- close bool;
- chunking bool
-}
-
-// Create new connection from rwc.
-func NewConn(rwc io.ReadWriteClose) (c *Conn, err *os.Error) {
- c = new(Conn);
- c.rwc = rwc;
- if c.br, err = bufio.NewBufRead(rwc); err != nil {
- return nil, err
- }
- if c.bw, err = bufio.NewBufWrite(rwc); err != nil {
- return nil, err
- }
- return c, nil
-}
-
-// Read next request from connection.
-func (c *Conn) ReadRequest() (req *Request, err *os.Error) {
- if req, err = ReadRequest(c.br); err != nil {
- return nil, err
- }
-
- // TODO: Proper handling of (lack of) Connection: close,
- // and chunked transfer encoding on output.
- c.close = true;
- return req, nil
-}
-
-// Close the connection.
-func (c *Conn) Close() {
- c.bw.Flush();
- c.rwc.Close();
-}
-
diff --git a/src/lib/http/request.go b/src/lib/http/request.go
index ba1a7a694f..5d1fd67d72 100644
--- a/src/lib/http/request.go
+++ b/src/lib/http/request.go
@@ -9,14 +9,15 @@ package http
import (
"bufio";
"http";
+ "io";
"os";
"strings"
)
const (
- _MaxLineLength = 1024; // assumed < bufio.DefaultBufSize
- _MaxValueLength = 1024;
- _MaxHeaderLines = 1024;
+ maxLineLength = 1024; // assumed < bufio.DefaultBufSize
+ maxValueLength = 1024;
+ maxHeaderLines = 1024;
)
var (
@@ -30,30 +31,36 @@ var (
// HTTP Request
type Request struct {
- method string; // GET, PUT,etc.
- rawurl string;
- url *URL; // URI after GET, PUT etc.
- proto string; // "HTTP/1.0"
- pmajor int; // 1
- pminor int; // 0
-
- header map[string] string;
-
- close bool;
- host string;
- referer string;
- useragent string;
+ Method string; // GET, PUT,etc.
+ RawUrl string;
+ Url *URL; // URI after GET, PUT etc.
+ Proto string; // "HTTP/1.0"
+ ProtoMajor int; // 1
+ ProtoMinor int; // 0
+
+ Header map[string] string;
+
+ Close bool;
+ Host string;
+ Referer string; // referer [sic]
+ UserAgent string;
}
+func (r *Request) ProtoAtLeast(major, minor int) bool {
+ return r.ProtoMajor > major ||
+ r.ProtoMajor == major && r.ProtoMinor >= minor
+}
+
+
// Read a line of bytes (up to \n) from b.
-// Give up if the line exceeds _MaxLineLength.
+// Give up if the line exceeds maxLineLength.
// The returned bytes are a pointer into storage in
// the bufio, so they are only valid until the next bufio read.
func readLineBytes(b *bufio.BufRead) (p []byte, err *os.Error) {
if p, err = b.ReadLineSlice('\n'); err != nil {
return nil, err
}
- if len(p) >= _MaxLineLength {
+ if len(p) >= maxLineLength {
return nil, LineTooLong
}
@@ -132,7 +139,7 @@ func readKeyValue(b *bufio.BufRead) (key, value string, err *os.Error) {
}
value += " " + string(line);
- if len(value) >= _MaxValueLength {
+ if len(value) >= maxValueLength {
return "", "", ValueTooLong
}
}
@@ -179,6 +186,37 @@ func parseHTTPVersion(vers string) (int, int, bool) {\n return major, minor, true\n }\n \n+var cmap = make(map[string]string)\n+\n+func CanonicalHeaderKey(s string) string {\n+\tif t, ok := cmap[s]; ok {\n+\t\treturn t;\n+\t}\n+\n+\t// canonicalize: first letter upper case\n+\t// and upper case after each dash.\n+\t// (Host, User-Agent, If-Modified-Since).\n+\t// HTTP headers are ASCII only, so no Unicode issues.\n+\ta := io.StringBytes(s);\n+\tupper := true;\n+\tfor i,v := range a {\n+\t\tif upper && 'a' <= v && v <= 'z' {\n+\t\t\ta[i] = v + 'A' - 'a';\n+\t\t}\n+\t\tif !upper && 'A' <= v && v <= 'Z' {\n+\t\t\ta[i] = v + 'a' - 'A';\n+\t\t}\n+\t\tupper = false;\n+\t\tif v == '-' {\n+\t\t\tupper = true;\n+\t\t}\n+\t}\n+\tt := string(a);\n+\tcmap[s] = t;\n+\treturn t;\n+}\n+\n+\n // Read and parse a request from b.\n func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {\n req = new(Request);\n@@ -193,19 +231,19 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {\n if f = strings.Split(s, " "); len(f) != 3 {\n return nil, BadRequest\n }\n- req.method, req.rawurl, req.proto = f[0], f[1], f[2];\n+ req.Method, req.RawUrl, req.Proto = f[0], f[1], f[2];\n var ok bool;\n- if req.pmajor, req.pminor, ok = parseHTTPVersion(req.proto); !ok {\n+ if req.ProtoMajor, req.ProtoMinor, ok = parseHTTPVersion(req.Proto); !ok {\n return nil, BadHTTPVersion\n }\n \n- if req.url, err = ParseURL(req.rawurl); err != nil {\n+ if req.Url, err = ParseURL(req.RawUrl); err != nil {\n return nil, err\n }\n \n // Subsequent lines: Key: value.\n nheader := 0;\n- req.header = make(map[string] string);\n+ req.Header = make(map[string] string);\n for {\n var key, value string;\n if key, value, err = readKeyValue(b); err != nil {\n@@ -214,18 +252,20 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {\n if key == "" {\n break\n }\n- if nheader++; nheader >= _MaxHeaderLines {\n+ if nheader++; nheader >= maxHeaderLines {\n return nil, HeaderTooLong\n }\n \n+ key = CanonicalHeaderKey(key);\n+\n // RFC 2616 says that if you send the same header key\n // multiple times, it has to be semantically equivalent\n // to concatenating the values separated by commas.\n- oldvalue, present := req.header[key];\n+ oldvalue, present := req.Header[key];\n if present {\n- req.header[key] = oldvalue+","+value\n+ req.Header[key] = oldvalue+","+value\n } else {\n- req.header[key] = value\n+ req.Header[key] = value\n }\n }\n \n@@ -236,40 +276,39 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {\n // GET http://www.google.com/index.html HTTP/1.1\n // Host: doesntmatter\n // the same. In the second case, any Host line is ignored.\n- if v, have := req.header["Host"]; have && req.url.host == "" {\n- req.host = v\n+ if v, present := req.Header["Host"]; present && req.Url.Host == "" {\n+ req.Host = v\n }\n \n // RFC2616: Should treat\n // Pragma: no-cache\n // like\n- // Cache-control: no-cache\n- if v, have := req.header["Pragma"]; have && v == "no-cache" {\n- if cc, havecc := req.header["Cache-control"]; !havecc {\n- req.header["Cache-control"] = "no-cache"\n+ // Cache-Control: no-cache\n+ if v, present := req.Header["Pragma"]; present && v == "no-cache" {\n+ if cc, presentcc := req.Header["Cache-Control"]; !presentcc {\n+ req.Header["Cache-Control"] = "no-cache"\n }\n }\n \n // Determine whether to hang up after sending the reply.\n- if req.pmajor < 1 || (req.pmajor == 1 && req.pminor < 1) {\n- req.close = true\n- } else if v, have := req.header["Connection"]; have {\n+ if req.ProtoMajor < 1 || (req.ProtoMajor == 1 && req.ProtoMinor < 1) {\n+ req.Close = true\n+ } else if v, present := req.Header["Connection"]; present {\n // TODO: Should split on commas, toss surrounding white space,\n // and check each field.\n if v == "close" {\n- req.close = true\n+ req.Close = true\n }\n }\n \n // Pull out useful fields as a convenience to clients.\n- if v, have := req.header["Referer"]; have {\n- req.referer = v\n+ if v, present := req.Header["Referer"]; present {\n+ req.Referer = v\n }\n- if v, have := req.header["User-Agent"]; have {\n- req.useragent = v\n+ if v, present := req.Header["User-Agent"]; present {\n+ req.UserAgent = v\n }\n \n-\n // TODO: Parse specific header values:\n // Accept\n // Accept-Encoding\ndiff --git a/src/lib/http/server.go b/src/lib/http/server.go
index 855eb98a59..970cd7e384 100644
--- a/src/lib/http/server.go
+++ b/src/lib/http/server.go
@@ -2,64 +2,448 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Trivial HTTP server
+// HTTP server. See RFC 2616.
-// TODO: Routines for writing responses.\n+// TODO(rsc):\n+// logging\n+// cgi support\n+// post support\n
package http
import (
+\t"bufio";\n+\t"fmt";\n+\t"http";\n \t"io";\n-\t"os";\n \t"net";\n-\t"http";\n+\t"os";\n \t"strconv";\n )\n \n-// Serve a new connection.\n-func serveConnection(fd net.Conn, raddr string, f func(*Conn, *Request)) {\n-\tc, err := NewConn(fd);\n-\tif err != nil {\n+var ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush")\n+\n+type Conn struct\n+\n+// Interface implemented by servers using this library.\n+type Handler interface {\n+\tServeHTTP(*Conn, *Request);\n+}\n+\n+// Active HTTP connection (server side).\n+type Conn struct {\n+\tFd io.ReadWriteClose;\n+\tRemoteAddr string;\n+\tReq *Request;\n+\tBr *bufio.BufRead;\n+\n+\tbr *bufio.BufRead;\n+\tbw *bufio.BufWrite;\n+\tclose bool;\n+\tchunking bool;\n+\tflushed bool;\n+\theader map[string] string;\n+\twroteHeader bool;\n+\thandler Handler;\n+}\n+\n+// HTTP response codes.\n+// TODO(rsc): Maybe move these to their own file, so that\n+// clients can use them too.\n+\n+const (\n+\tStatusContinue = 100;\n+\tStatusSwitchingProtocols = 101;\n+\n+\tStatusOK = 200;\n+\tStatusCreated = 201;\n+\tStatusAccepted = 202;\n+\tStatusNonAuthoritativeInfo = 203;\n+\tStatusNoContent = 204;\n+\tStatusResetContent = 205;\n+\tStatusPartialContent = 206;\n+\n+\tStatusMultipleChoices = 300;\n+\tStatusMovedPermanently = 301;\n+\tStatusFound = 302;\n+\tStatusSeeOther = 303;\n+\tStatusNotModified = 304;\n+\tStatusUseProxy = 305;\n+\tStatusTemporaryRedirect = 307;\n+\n+\tStatusBadRequest = 400;\n+\tStatusUnauthorized = 401;\n+\tStatusPaymentRequired = 402;\n+\tStatusForbidden = 403;\n+\tStatusNotFound = 404;\n+\tStatusMethodNotAllowed = 405;\n+\tStatusNotAcceptable = 406;\n+\tStatusProxyAuthRequired = 407;\n+\tStatusRequestTimeout = 408;\n+\tStatusConflict = 409;\n+\tStatusGone = 410;\n+\tStatusLengthRequired = 411;\n+\tStatusPreconditionFailed = 412;\n+\tStatusRequestEntityTooLarge = 413;\n+\tStatusRequestURITooLong = 414;\n+\tStatusUnsupportedMediaType = 415;\n+\tStatusRequestedRangeNotSatisfiable = 416;\n+\tStatusExpectationFailed = 417;\n+\n+\tStatusInternalServerError = 500;\n+\tStatusNotImplemented = 501;\n+\tStatusBadGateway = 502;\n+\tStatusServiceUnavailable = 503;\n+\tStatusGatewayTimeout = 504;\n+\tStatusHTTPVersionNotSupported = 505;\n+)\n+\n+var statusText = map[int]string {\n+\tStatusContinue:\t\t\t"Continue",\n+\tStatusSwitchingProtocols:\t"Switching Protocols",\n+\n+\tStatusOK:\t\t\t"OK",\n+\tStatusCreated:\t\t\t"Created",\n+\tStatusAccepted:\t\t\t"Accepted",\n+\tStatusNonAuthoritativeInfo:\t"Non-Authoritative Information",\n+\tStatusNoContent:\t\t"No Content",\n+\tStatusResetContent:\t\t"Reset Content",\n+\tStatusPartialContent:\t\t"Partial Content",\n+\n+\tStatusMultipleChoices:\t\t"Multiple Choices",\n+\tStatusMovedPermanently:\t\t"Moved Permanently",\n+\tStatusFound:\t\t\t"Found",\n+\tStatusSeeOther:\t\t\t"See Other",\n+\tStatusNotModified:\t\t"Not Modified",\n+\tStatusUseProxy:\t\t\t"Use Proxy",\n+\tStatusTemporaryRedirect:\t"Temporary Redirect",\n+\n+\tStatusBadRequest:\t\t"Bad Request",\n+\tStatusUnauthorized:\t\t"Unauthorized",\n+\tStatusPaymentRequired:\t\t"Payment Required",\tStatusForbidden:\t\t"Forbidden",\n+\tStatusNotFound:\t\t\t"Not Found",\n+\tStatusMethodNotAllowed:\t\t"Method Not Allowed",\n+\tStatusNotAcceptable:\t\t"Not Acceptable",\n+\tStatusProxyAuthRequired:\t"Proxy Authentication Required",\n+\tStatusRequestTimeout:\t\t"Request Timeout",\n+\tStatusConflict:\t\t\t"Conflict",\n+\tStatusGone:\t\t\t"Gone",\n+\tStatusLengthRequired:\t\t"Length Required",\n+\tStatusPreconditionFailed:\t"Precondition Failed",\n+\tStatusRequestEntityTooLarge:\t"Request Entity Too Large",\n+\tStatusRequestURITooLong:\t"Request URI Too Long",\n+\tStatusUnsupportedMediaType:\t"Unsupported Media Type",\n+\tStatusRequestedRangeNotSatisfiable:\t"Requested Range Not Satisfiable",\n+\tStatusExpectationFailed:\t"Expectation Failed",\n+\n+\tStatusInternalServerError:\t"Internal Server Error",\n+\tStatusNotImplemented:\t\t"Not Implemented",\n+\tStatusBadGateway:\t\t"Bad Gateway",\n+\tStatusServiceUnavailable:\t"Service Unavailable",\n+\tStatusGatewayTimeout:\t\t"Gateway Timeout",\n+\tStatusHTTPVersionNotSupported:\t"HTTP Version Not Supported",\n+}\n+\n+// Create new connection from rwc.\n+func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) {\n+\tc = new(Conn);\n+\tc.Fd = rwc;\n+\tc.RemoteAddr = raddr;\n+\tc.handler = handler;\n+\tif c.br, err = bufio.NewBufRead(rwc.(io.Read)); err != nil {\n+\t\treturn nil, err\n+\t}\n+c.Br = c.br;\n+\tif c.bw, err = bufio.NewBufWrite(rwc); err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn c, nil\n+}\n+\n+func (c *Conn) SetHeader(hdr, val string)\n+\n+// Read next request from connection.\n+func (c *Conn) readRequest() (req *Request, err *os.Error) {\n+\tif req, err = ReadRequest(c.br); err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\t// Reset per-request connection state.\n+\tc.header = make(map[string] string);\n+\tc.wroteHeader = false;\n+\tc.flushed = false;\n+\tc.Req = req;\n+\n+\t// Default output is HTML encoded in UTF-8.\n+\tc.SetHeader("Content-Type", "text/html; charset=utf-8");\n+\n+\tif req.ProtoAtLeast(1, 1) {\n+\t\t// HTTP/1.1 or greater: use chunked transfer encoding\n+\t\t// to avoid closing the connection at EOF.\n+\t\tc.chunking = true;\n+\t\tc.SetHeader("Transfer-Encoding", "chunked");\n+\t} else {\n+\t\t// HTTP version < 1.1: cannot do chunked transfer\n+\t\t// encoding, so signal EOF by closing connection.\n+\t\t// Could avoid closing the connection if there is\n+\t\t// a Content-Length: header in the response,\n+\t\t// but everyone who expects persistent connections\n+\t\t// does HTTP/1.1 now.\n+\t\tc.close = true;\n+\t\tc.chunking = false;\n+\t}\n+\n+\treturn req, nil\n+}\n+\n+func (c *Conn) SetHeader(hdr, val string) {\n+\tc.header[CanonicalHeaderKey(hdr)] = val;\n+}\n+\n+// Write header.\n+func (c *Conn) WriteHeader(code int) {\n+\tif c.wroteHeader {\n+\t\t// TODO(rsc): log\n+\t\treturn\n+\t}\n+\tc.wroteHeader = true;\n+\tif !c.Req.ProtoAtLeast(1, 0) {\n+\t\treturn\n+\t}\n+\tproto := "HTTP/1.0";\n+\tif c.Req.ProtoAtLeast(1, 1) {\n+\t\tproto = "HTTP/1.1";\n+\t}\n+\tcodestring := strconv.Itoa(code);\n+\ttext, ok := statusText[code];\n+\tif !ok {\n+\t\ttext = "status code " + codestring;\n+\t}\n+\tio.WriteString(c.bw, proto + " " + codestring + " " + text + "\\r\\n");\n+\tfor k,v := range c.header {\n+\t\tio.WriteString(c.bw, k + ": " + v + "\\r\\n");\n+\t}\n+\tio.WriteString(c.bw, "\\r\\n");\n+}\n+\n+// TODO(rsc): BUG in 6g: must return "nn int" not "n int"\n+// so that the implicit struct assignment in\n+// return c.bw.Write(data) works. oops\n+func (c *Conn) Write(data []byte) (nn int, err *os.Error) {\n+\tif c.flushed {\n+\t\treturn 0, ErrWriteAfterFlush\n+\t}\n+\tif !c.wroteHeader {\n+\t\tc.WriteHeader(StatusOK);\n+\t}\n+\tif len(data) == 0 {\n+\t\treturn 0, nil\n+\t}\n+\n+\t// TODO(rsc): if chunking happened after the buffering,\n+\t// then there would be fewer chunk headers\n+\tif c.chunking {\n+\t\tfmt.Fprintf(c.bw, "%x\\r\\n", len(data));\t// TODO(rsc): use strconv not fmt\n+\t}\n+\treturn c.bw.Write(data);\n+}\n+\n+func (c *Conn) Flush() {\n+\tif c.flushed {\n \t\treturn\n \t}\n+\tif !c.wroteHeader {\n+\t\tc.WriteHeader(StatusOK);\n+\t}\n+\tif c.chunking {\n+\t\tio.WriteString(c.bw, "0\\r\\n");\n+\t\t// trailer key/value pairs, followed by blank line\n+\t\tio.WriteString(c.bw, "\\r\\n");\n+\t}\n+\tc.bw.Flush();\n+\tc.flushed = true;\n+}\n+\n+// Close the connection.\n+func (c *Conn) Close() {\n+\tif c.bw != nil {\n+\t\tc.bw.Flush();\n+\t\tc.bw = nil;\n+\t}\n+\tif c.Fd != nil {\n+\t\tc.Fd.Close();\n+\t\tc.Fd = nil;\n+\t}\n+}\n+\n+// Serve a new connection.\n+func (c *Conn) serve() {\n \tfor {\n-\t\treq, err := c.ReadRequest();\n+\t\treq, err := c.readRequest();\n \t\tif err != nil {\n \t\t\tbreak\n \t\t}\n-\t\tf(c, req);\n+\t\t// HTTP cannot have multiple simultaneous active requests.\n+\t\t// Until the server replies to this request, it can't read another,\n+\t\t// so we might as well run the handler in this thread.\n+\t\tc.handler.ServeHTTP(c, req);\n+\t\tif c.Fd == nil {\n+\t\t\t// Handler took over the connection.\n+\t\t\treturn;\n+\t\t}\n+\t\tif !c.flushed {\n+\t\t\tc.Flush();\n+\t\t}\n \t\tif c.close {\n-\t\t\tbreak\n+\t\t\tbreak;\n \t\t}\n \t}\n \tc.Close();\n }\n \n-// Web server: already listening on l, call f for each request.\n-func Serve(l net.Listener, f func(*Conn, *Request)) *os.Error {\n-\t// TODO: Make this unnecessary\n-\ts, e := os.Getenv("GOMAXPROCS");\n-\tif n, ok := strconv.Atoi(s); n < 3 {\n-\t\tprint("Warning: $GOMAXPROCS needs to be at least 3.\\n");\n+// Adapter: can use RequestFunction(f) as Handler\n+type handlerFunc struct {\n+\tf func(*Conn, *Request)\n+}\n+func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {\n+\th.f(c, req)\n+}\n+func HandlerFunc(f func(*Conn, *Request)) Handler {\n+\treturn handlerFunc{f}\n+}\n+\n+/* simpler version of above, not accepted by 6g:\n+\n+type HandlerFunc func(*Conn, *Request)\n+func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {\n+\tf(c, req);\n+}\n+*/\n+\n+// Helper handlers\n+\n+// 404 not found\n+func notFound(c *Conn, req *Request) {\n+\tc.SetHeader("Content-Type", "text/plain; charset=utf-8");\n+\tc.WriteHeader(StatusNotFound);\n+\tio.WriteString(c, "404 page not found\\n");\n+}\n+\n+var NotFoundHandler = HandlerFunc(notFound)\n+\n+// Redirect to a fixed URL\n+type redirectHandler struct {\n+\tto string;\n+}\n+func (h *redirectHandler) ServeHTTP(c *Conn, req *Request) {\n+\tc.SetHeader("Location", h.to);\n+\tc.WriteHeader(StatusMovedPermanently);\n+}\n+\n+func RedirectHandler(to string) Handler {\n+\treturn &redirectHandler{to};\n+}\n+\n+// Path-based HTTP request multiplexer.\n+// Patterns name fixed paths, like "/favicon.ico",\n+// or subtrees, like "/images/".\n+// For now, patterns must begin with /.\n+// Eventually, might want to allow host name\n+// at beginning of pattern, so that you could register\n+// /codesearch\n+// codesearch.google.com/\n+// but not take over /.\n+\n+type ServeMux struct {\n+\tm map[string] Handler\n+}\n+\n+func NewServeMux() *ServeMux {\n+\treturn &ServeMux{make(map[string] Handler)};\n+}\n+\n+var DefaultServeMux = NewServeMux();\n+\n+// Does path match pattern?\n+func pathMatch(pattern, path string) bool {\n+\tif len(pattern) == 0 {\n+\t\t// should not happen\n+\t\treturn false\n+\t}\n+\tn := len(pattern);\n+\tif pattern[n-1] != '/' {\n+\t\treturn pattern == path\n \t}\n+\treturn len(path) >= n && path[0:n] == pattern;\n+}\n+\n+func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) {\n+\t// Most-specific (longest) pattern wins.\n+\tvar h Handler;\n+\tvar n = 0;\n+\tfor k, v := range mux.m {\n+\t\tif !pathMatch(k, req.Url.Path) {\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif h == nil || len(k) > n {\n+\t\t\tn = len(k);\n+\t\t\th = v;\n+\t\t}\n+\t}\n+\tif h == nil {\n+\t\th = NotFoundHandler;\n+\t}\n+\th.ServeHTTP(c, req);\n+}\n \n+func (mux *ServeMux) Handle(pattern string, handler Handler) {\n+\tif pattern == "" || pattern[0] != '/' {\n+\t\tpanicln("http: invalid pattern", pattern);\n+\t}\n+\n+\tmux.m[pattern] = handler;\n+\n+\t// Helpful behavior:\n+\t// If pattern is /tree/, insert redirect for /tree.\n+\tn := len(pattern);\n+\tif n > 0 && pattern[n-1] == '/' {\n+\t\tmux.m[pattern[0:n-1]] = RedirectHandler(pattern);\n+\t}\n+}\n+\n+func Handle(pattern string, h Handler) {\n+\tDefaultServeMux.Handle(pattern, h);\n+}\n+\n+\n+// Web server: listening on l, call handler.ServeHTTP for each request.\n+func Serve(l net.Listener, handler Handler) *os.Error {\n+\tif handler == nil {\n+\t\thandler = DefaultServeMux;\n+\t}\n \tfor {\n \t\trw, raddr, e := l.Accept();\n \t\tif e != nil {\n \t\t\treturn e\n \t\t}\n-\t\tgo serveConnection(rw, raddr, f)\n+\t\tc, err := newConn(rw, raddr, handler);\n+\t\tif err != nil {\n+\t\t\tcontinue;\n+\t\t}\n+\t\tgo c.serve();\n \t}\n \tpanic("not reached")\n }\n \n // Web server: listen on address, call f for each request.\n-func ListenAndServe(addr string, f func(*Conn, *Request)) *os.Error {\n+func ListenAndServe(addr string, handler Handler) *os.Error {\n l, e := net.Listen("tcp", addr);\n if e != nil {\n return e\n }\n- e = Serve(l, f);\n+ e = Serve(l, handler);\n l.Close();\n return e\n }\n+\ndiff --git a/src/lib/http/triv.go b/src/lib/http/triv.go
index a7eb35aa2b..136100135a 100644
--- a/src/lib/http/triv.go
+++ b/src/lib/http/triv.go
@@ -5,24 +5,52 @@
package main
import (
-\t"io";\n \t"bufio";\n-\t"os";\n+\t"flag";\n+\t"fmt";\n+\t"http";\n+\t"io";\n "net";\n-\t"http"\n+\t"os";\n )\n \n-func Echo(conn *http.Conn, req *http.Request) {\n-\tfd := conn.bw;\n-\tconn.close = true;\n-\tio.WriteString(fd, "HTTP/1.1 200 OK\\r\\n"\n-\t\t"Content-Type: text/plain\\r\\n"\n-\t\t"\\r\\n");\n-\tio.WriteString(fd, req.method+" "+req.rawurl+" "+req.proto+"\\r\\n")\n+\n+// hello world, the web server\n+func HelloServer(c *http.Conn, req *http.Request) {\n+\tio.WriteString(c, "hello, world!\\n");\n+}\n+\n+// simple counter server\n+type Counter struct {\n+\tn int;\n+}\n+\n+func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {\n+\tfmt.Fprintf(c, "counter = %d\\n", ctr.n);\n+\tctr.n++;\n+}\n+\n+// simple file server\n+var webroot = flag.String("root", "/home/rsc", "web root directory")\n+func FileServer(c *http.Conn, req *http.Request) {\n+\tc.SetHeader("content-type", "text/plain; charset=utf-8");\n+\tpath := *webroot + req.Url.Path;\t// TODO: insecure: use os.CleanName\n+\tfd, err := os.Open(path, os.O_RDONLY, 0);\n+\tif err != nil {\n+\t\tc.WriteHeader(http.StatusNotFound);\n+\t\tfmt.Fprintf(c, "open %s: %v\\n", path, err);\n+\t\treturn;\n+\t}\n+\tn, err1 := io.Copy(fd, c);\n+\tfmt.Fprintf(c, "[%d bytes]\\n", n);\n }\n \n func main() {\n-\terr := http.ListenAndServe("0.0.0.0:12345", &Echo);\n+\tflag.Parse();\n+\thttp.Handle("/counter", new(Counter));\n+\thttp.Handle("/go/", http.HandlerFunc(FileServer));\n+\thttp.Handle("/go/hello", http.HandlerFunc(HelloServer));\n+\terr := http.ListenAndServe(":12345", nil);\n if err != nil {\n panic("ListenAndServe: ", err.String())\n }\ndiff --git a/src/lib/http/url.go b/src/lib/http/url.go
index 9c1a94e2b9..f0a94d68bc 100644
--- a/src/lib/http/url.go
+++ b/src/lib/http/url.go
@@ -77,15 +77,15 @@ func URLUnescape(s string) (string, *os.Error) {\n }\n \n type URL struct {\n-\traw string;\n-\tscheme string;\n-\trawpath string;\n-\tauthority string;\n-\tuserinfo string;\n-\thost string;\n-\tpath string;\n-\tquery string;\n-\tfragment string;\n+\tRaw string;\n+\tScheme string;\n+\tRawPath string;\n+\tAuthority string;\n+\tUserinfo string;\n+\tHost string;\n+\tPath string;\n+\tQuery string;\n+\tFragment string;\n }\n \n // Maybe rawurl is of the form scheme:path.\n@@ -132,39 +132,39 @@ func ParseURL(rawurl string) (url *URL, err *os.Error) {\n \t\treturn nil, BadURL\n \t}\n \turl = new(URL);\n-\turl.raw = rawurl;\n+\turl.Raw = rawurl;\n \n \t// split off possible leading "http:", "mailto:", etc.\n \tvar path string;\n-\tif url.scheme, path, err = getscheme(rawurl); err != nil {\n+\tif url.Scheme, path, err = getscheme(rawurl); err != nil {\n \t\treturn nil, err\n \t}\n-\turl.rawpath = path;\n+\turl.RawPath = path;\n \n \t// RFC 2396: a relative URI (no scheme) has a ?query,\n \t// but absolute URIs only have query if path begins with /\n-\tif url.scheme == "" || len(path) > 0 && path[0] == '/' {\n-\t\tpath, url.query = split(path, '?', true);\n-\t\tif url.query, err = URLUnescape(url.query); err != nil {\n+\tif url.Scheme == "" || len(path) > 0 && path[0] == '/' {\n+\t\tpath, url.Query = split(path, '?', true);\n+\t\tif url.Query, err = URLUnescape(url.Query); err != nil {\n \t\t\treturn nil, err\n \t\t}\n \t}\n \n \t// Maybe path is //authority/path\n \tif len(path) > 2 && path[0:2] == "//" {\n-\t\turl.authority, path = split(path[2:len(path)], '/', false);\n+\t\turl.Authority, path = split(path[2:len(path)], '/', false);\n \t}\n \n \t// If there's no @, split's default is wrong. Check explicitly.\n-\tif strings.Index(url.authority, "@") < 0 {\n-\t\turl.host = url.authority;\n+\tif strings.Index(url.Authority, "@") < 0 {\n+\t\turl.Host = url.Authority;\n \t} else {\n-\t\turl.userinfo, url.host = split(url.authority, '@', true);\n+\t\turl.Userinfo, url.Host = split(url.Authority, '@', true);\n \t}\n \n \t// What's left is the path.\n \t// TODO: Canonicalize (remove . and ..)?\n-\tif url.path, err = URLUnescape(path); err != nil {\n+\tif url.Path, err = URLUnescape(path); err != nil {\n \t\treturn nil, err\n \t}\n \n@@ -178,7 +178,7 @@ func ParseURLReference(rawurlref string) (url *URL, err *os.Error) {\n if url, err = ParseURL(rawurl); err != nil {\n return nil, err\n }\n- if url.fragment, err = URLUnescape(frag); err != nil {\n+ if url.Fragment, err = URLUnescape(frag); err != nil {\n return nil, err\n }\n return url, nil
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e73acc1b35f3490f3d800b4bf49da79630e808fc
元コミット内容
flesh out http server.
convert to uppercase names.
R=r
DELTA=613 (460 added, 61 deleted, 92 changed)
OCL=24139
CL=24145
変更の背景
このコミットは、Go言語の初期開発段階(2009年)において、net/http
パッケージがどのように進化していったかを示す重要なマイルストーンです。Go言語は、シンプルさ、効率性、堅牢性を重視し、特に並行処理を言語のコア機能として提供することを目指していました。net/http
パッケージもこの設計思想を強く反映しており、ウェブサーバーやクライアントを構築するためのシンプルで強力なツールを提供することを目指していました。
この時期の変更は、Goの標準ライブラリが「外部依存にほとんど頼らずに一般的なプログラミングタスクを処理できる包括的で有能なライブラリ」であるべきという目標に沿っています。特に、net/http
パッケージは、http.Handler
やhttp.ResponseWriter
といったインターフェースを中心に設計されており、これにより高い構成可能性と拡張性を実現しています。
このコミットの主な目的は、HTTPサーバーの機能を「肉付け」し、より実用的なものにすることでした。これには、HTTPプロトコルの詳細な処理、エラーハンドリング、そしてGo言語の命名規則(エクスポートされる識別子は大文字で始まる)への準拠が含まれます。
前提知識の解説
HTTPプロトコル
HTTP (Hypertext Transfer Protocol) は、ウェブ上でデータを交換するためのプロトコルです。クライアント(ウェブブラウザなど)がサーバーにリクエストを送信し、サーバーがレスポンスを返すという「リクエスト-レスポンス」モデルに基づいています。
- リクエスト: クライアントがサーバーに送信する情報。メソッド(GET, POSTなど)、URI、HTTPバージョン、ヘッダー(Host, User-Agentなど)、およびオプションでボディ(POSTリクエストの場合)が含まれます。
- レスポンス: サーバーがクライアントに返す情報。HTTPバージョン、ステータスコード(200 OK, 404 Not Foundなど)、ステータスフレーズ、ヘッダー(Content-Type, Content-Lengthなど)、およびオプションでボディ(HTMLコンテンツなど)が含まれます。
- ヘッダー: リクエストやレスポンスに関するメタデータを提供するキーと値のペア。
Go言語の命名規則
Go言語では、識別子(変数、関数、型など)の最初の文字が大文字であるか小文字であるかによって、その可視性(エクスポートされるか否か)が決定されます。
- 大文字で始まる識別子: パッケージ外からアクセス可能(エクスポートされる)。
- 小文字で始まる識別子: パッケージ内でのみアクセス可能(エクスポートされない)。
このコミットでは、net/http
パッケージの外部から利用されるべき構造体のフィールドやメソッドが、この規則に従って小文字から大文字に変更されています。
Go言語のインターフェース
Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。特定のインターフェースのすべてのメソッドを実装する型は、そのインターフェースを満たすと見なされます。これにより、Goは「継承よりもコンポジション」という設計原則を促進し、柔軟で再利用可能なコードを記述できます。
net/http
パッケージでは、http.Handler
インターフェースが中心的な役割を果たします。これはServeHTTP(c *Conn, req *Request)
という単一のメソッドを定義しており、HTTPリクエストを処理するための統一された方法を提供します。
HTTPステータスコード
HTTPステータスコードは、HTTPリクエストが成功したか、失敗したか、または追加のアクションが必要かを示す3桁の整数です。例えば、200 OK
は成功、404 Not Found
はリソースが見つからないことを示します。
チャンク転送エンコーディング (Chunked Transfer Encoding)
HTTP/1.1で導入された転送エンコーディングの一種で、メッセージボディを固定長ではなく、一連の「チャンク」として送信することを可能にします。これにより、サーバーはレスポンス全体のサイズを事前に知らなくても、動的にコンテンツを生成して送信できます。これは、特に大きなファイルやストリーミングデータの場合に有用です。
技術的詳細
このコミットは、net/http
パッケージのサーバーサイドの機能を大幅に拡張しています。
-
Conn
構造体の変更とconn.go
の削除:- 以前は
src/lib/http/conn.go
に独立して定義されていたConn
構造体が、src/lib/http/server.go
に統合されました。これは、HTTP接続の管理がサーバーの主要な責務の一部であることを明確にするためと考えられます。 Conn
構造体には、Fd
(ファイルディスクリプタ/ネットワーク接続)、RemoteAddr
(リモートアドレス)、Req
(現在のリクエスト)、Br
(バッファリングされたリーダー)、Bw
(バッファリングされたライター) など、接続の状態を管理するためのフィールドが追加されました。close
、chunking
、flushed
、header
、wroteHeader
、handler
といった内部状態管理用のフィールドも追加され、より複雑なHTTPプロトコル処理に対応できるようになりました。
- 以前は
-
命名規則の統一(大文字化):
Request
構造体のフィールド(例:method
->Method
,rawurl
->RawUrl
,header
->Header
)や、URL
構造体のフィールドが、Goのエクスポート規則に従って小文字から大文字に変更されました。これにより、これらのフィールドがパッケージ外部からアクセス可能になり、APIの使いやすさが向上しました。- 内部定数も
_MaxLineLength
からmaxLineLength
のように変更され、Goの慣習に沿うようになりました。
-
http.Handler
インターフェースの導入:- HTTPリクエストを処理するための中心的なインターフェースとして
Handler
が導入されました。これはServeHTTP(*Conn, *Request)
メソッドを定義しており、これにより異なるハンドラを柔軟に組み合わせることが可能になりました。 HandlerFunc
型が導入され、通常の関数をHandler
インターフェースとしてラップできるようになりました。これは、Goにおける関数型プログラミングのパターンの一つです。
- HTTPリクエストを処理するための中心的なインターフェースとして
-
ServeMux
(HTTPリクエストマルチプレクサ) の導入:ServeMux
は、受信したHTTPリクエストのURLパスに基づいて適切なHandler
にルーティングするための機能を提供します。これにより、単一のHTTPサーバーで複数の異なるパスに対するリクエストを処理できるようになります。Handle(pattern string, handler Handler)
メソッドを通じて、特定のパターンとハンドラを関連付けることができます。pathMatch
関数は、リクエストパスがパターンに一致するかどうかを判断するためのロジックを提供します。最も具体的な(最長の)パターンが優先されるようになっています。
-
HTTPステータスコードの定数化と管理:
- HTTPステータスコード(例:
StatusOK
,StatusNotFound
)が定数として定義され、statusText
マップによって対応するテキスト表現が提供されるようになりました。これにより、レスポンスのステータス設定がより明確かつエラーなく行えるようになりました。
- HTTPステータスコード(例:
-
レスポンス書き込み機能の強化:
Conn
構造体にSetHeader
,WriteHeader
,Write
,Flush
といったメソッドが追加され、HTTPレスポンスのヘッダー設定、ステータスコードの送信、ボディの書き込み、バッファのフラッシュといった操作がより細かく制御できるようになりました。- 特に
Write
メソッドでは、HTTP/1.1のチャンク転送エンコーディングがサポートされ、大きなレスポンスボディを効率的に送信できるようになりました。
-
triv.go
(サンプルサーバー) の更新:triv.go
は、新しいnet/http
パッケージのAPIを使用するように更新されました。HelloServer
、Counter
(カウンタ機能を持つハンドラ)、FileServer
(ファイルサーバー) といった具体的なハンドラの例が追加され、ServeMux
を使ったルーティングのデモンストレーションが行われています。
これらの変更により、net/http
パッケージは、単なる「おもちゃ」のHTTPサーバーから、より実用的で拡張性の高い、Goらしいウェブアプリケーション開発の基盤へと進化しました。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
src/lib/http/conn.go
: 削除されました。その機能はsrc/lib/http/server.go
に統合されました。src/lib/http/request.go
:Request
構造体のフィールド名が小文字から大文字に変更されました(例:method
->Method
)。- HTTPバージョンを比較する
ProtoAtLeast
メソッドが追加されました。 - HTTPヘッダーのキーを正規化する
CanonicalHeaderKey
関数が追加されました。 - ヘッダーの処理ロジックが更新され、正規化されたキーを使用し、重複するヘッダーの値をカンマで結合するようになりました。
src/lib/http/server.go
:Conn
構造体が再定義され、HTTP接続の状態管理とレスポンス書き込みのための多くの新しいフィールドとメソッドが追加されました。Handler
インターフェース、HandlerFunc
型、ServeMux
構造体、および関連するルーティングロジックが導入されました。- HTTPステータスコードの定数と、それに対応するテキストマップが追加されました。
Serve
およびListenAndServe
関数が、Handler
インターフェースを受け入れるように変更されました。- チャンク転送エンコーディングのサポートが追加されました。
src/lib/http/triv.go
:- 新しい
net/http
パッケージのAPIを使用するようにサンプルサーバーが更新されました。 HelloServer
,Counter
,FileServer
といった新しいハンドラの例が追加され、http.Handle
を使ったルーティングが示されています。
- 新しい
src/lib/http/url.go
:URL
構造体のフィールド名が小文字から大文字に変更されました(例:raw
->Raw
,scheme
->Scheme
)。
コアとなるコードの解説
src/lib/http/server.go
におけるConn
構造体と関連メソッド
type Conn struct {
Fd io.ReadWriteClose;
RemoteAddr string;
Req *Request;
Br *bufio.BufRead;
br *bufio.BufRead;
bw *bufio.BufWrite;
close bool;
chunking bool;
flushed bool;
header map[string] string;
wroteHeader bool;
handler Handler;
}
// HTTPレスポンスヘッダーを設定
func (c *Conn) SetHeader(hdr, val string) {
c.header[CanonicalHeaderKey(hdr)] = val;
}
// HTTPレスポンスヘッダーを書き込み
func (c *Conn) WriteHeader(code int) {
// ... (ステータスラインとヘッダーの書き込みロジック)
}
// レスポンスボディを書き込み
func (c *Conn) Write(data []byte) (nn int, err *os.Error) {
// ... (チャンク転送エンコーディングの処理を含むボディ書き込みロジック)
}
// バッファをフラッシュ
func (c *Conn) Flush() {
// ... (バッファのフラッシュとチャンク転送エンコーディングの終了処理)
}
// 接続を処理するメインループ
func (c *Conn) serve() {
for {
req, err := c.readRequest();
// ... (リクエスト読み込み、ハンドラ呼び出し、レスポンス送信、接続クローズのロジック)
}
}
Conn
構造体は、個々のHTTP接続の状態を保持し、リクエストの読み込み、レスポンスの書き込み、ヘッダーの管理、チャンク転送エンコーディングの処理など、HTTPプロトコルレベルの操作をカプセル化します。serve()
メソッドは、この接続上でリクエストを継続的に処理するゴルーチン内で実行されます。
src/lib/http/server.go
におけるHandler
インターフェースとServeMux
// Interface implemented by servers using this library.
type Handler interface {
ServeHTTP(*Conn, *Request);
}
// Adapter: can use RequestFunction(f) as Handler
type handlerFunc struct {
f func(*Conn, *Request)
}
func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {
h.f(c, req)
}
func HandlerFunc(f func(*Conn, *Request)) Handler {
return handlerFunc{f}
}
type ServeMux struct {
m map[string] Handler
}
func NewServeMux() *ServeMux {
return &ServeMux{make(map[string] Handler)};
}
func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) {
// Most-specific (longest) pattern wins.
// ... (パスに基づいて最適なハンドラを選択するロジック)
h.ServeHTTP(c, req);
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
// ... (パターンとハンドラをマップに登録するロジック)
}
func Handle(pattern string, h Handler) {
DefaultServeMux.Handle(pattern, h);
}
Handler
インターフェースは、HTTPリクエストを処理するための抽象化を提供します。ServeMux
は、このHandler
インターフェースを利用して、異なるURLパスに異なるハンドラをマッピングするルーティング機能を実現します。Handle
関数は、デフォルトのServeMux
にハンドラを登録するための便利なショートカットです。
src/lib/http/request.go
におけるCanonicalHeaderKey
var cmap = make(map[string]string)
func CanonicalHeaderKey(s string) string {
if t, ok := cmap[s]; ok {
return t;
}
// canonicalize: first letter upper case
// and upper case after each dash.
// (Host, User-Agent, If-Modified-Since).
// HTTP headers are ASCII only, so no Unicode issues.
a := io.StringBytes(s);
upper := true;
for i,v := range a {
if upper && 'a' <= v && v <= 'z' {
a[i] = v + 'A' - 'a';
}
if !upper && 'A' <= v && v <= 'Z' {
a[i] = v + 'a' - 'A';
}
upper = false;
if v == '-' {
upper = true;
}
}
t := string(a);
cmap[s] = t;
return t;
}
CanonicalHeaderKey
関数は、HTTPヘッダーのキーを標準的な形式(例: "content-type" を "Content-Type" に)に変換するために使用されます。これは、HTTPヘッダーのキーが大文字と小文字を区別しないというRFCの規定に対応し、ヘッダーの検索と処理を容易にするために重要です。cmap
は、変換結果をキャッシュしてパフォーマンスを向上させています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/
- Go言語
net/http
パッケージドキュメント: https://pkg.go.dev/net/http (現在のバージョン) - RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1: https://datatracker.ietf.org/doc/html/rfc2616
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHYC0z1RnO98K27CqskDm-JFmQsRozkWdIer4loZ8B6HS2s7DoHrXoxP4I3ji9YnfDU8X4EuWoeoaSAOrTwpX9IwYUxe_1IQ33cg2jEM9GW1IECrw==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHhOh4zy_5X29_vR5kMAWBWi-7ayj-Jg9sE21GQWe-4-ydyOSBLiTtFaAslj10vvi71PKLUX9PcmTl2e7Z0pA9Y2Nhy9A7zAzCGVi6B7_P6ZNYyImt36QXKeY3w0qo3THMtY0oD5sfs8CVOiW_Iswog2-NLBfbFnjIk_3dO
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGDzO-G52E9IP6IepEMOG9sOHVIjH1Tw2jGDiZijZPOjP7jWwOLItKhWG0VbJyIEuRkRmxg3LJNdKVR-R2DR9rVp_mManr_WDQX5WqhF5yAfOWwuPLfY7sZeeosJ7nVnRBZDVsASfQ6CEmz8Cu2LzDDd_c1bD2zhKFlEBQ=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHzrx8B29gM0OiA4QZ6gG6jaDYcAWkuprwWgYG6BjwouGXJf5RFOHl3Y1IXGOavTeDaXAIQWCEAPWzbVqqQtRycSEcoXS6s06sqUl8CiOfgy3p_q22FaxTBwmGjUHhS8m4ykRqE0dfCowuZlky9ECJ5rz4ndsU_qAxys378dCY=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGajsZzI3A0ZwwRjFrzN2MtSitkm7WaKL29vEG4K1M5DQwI_yg9mLPgH87PVfJBfHq6b3yxu0Q4Z2hrGNGGSMCXRftD8ZObyNfNWAJ_bYpRUq1yUuNwNaD-16czWVLI91eJxTg96ZG3YcGTcOl3xwGkAKmFvA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHONgDZ7ZWIxvh2GmpdK3ZVZ-SCSezFwkEeDw5Gz-37eQJA6tBjW5740Dr9mdhJQ8GBTxlEcMnVKE_V_Y_XJCCrvM9abse7V_6aVPeuU_XlcKp-Q51CADwhc_voODTWLfByMhj8lQkl-nhZQYPTOPp_cN5vIwAklx0mQSPHBH95t4711Xk=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF0MUgmxT_P88fzyRf96-l_YIKzQqiynjWunbORGEFFnUT7NJ462Iuli2PNs0UGJgGHBDUUlk_hpAaw_z5P1tT9yZpPM2WaSRXeN-En-zmwvpIm5fByZSJs5Tx_ulnuNerN4K2o3eenmGxW5IvppeyQsi04lVn3HGzqUkuxBDtIjwtzmCF9MgzwxdRqE4HJOHd97M1OtXPVmE1UgsPr6o9a1g==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEb5GLLUne6B4NxXb5lYGvp4VWRxWNJCcR3IA6aUk2BvOGJKBIMxnvn7_5wMXwubhS1z3YtuAHZpTvy09HvlOcLw3xf_eNfRc7Fv1Loj7lVnkGEGtF7hVnldG160rbRKdBO1lji6R0Ogp5DG8PqcJvHplMASxY
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHDOK-9Wym5Jsa_eQwvXl4wGjww81mOQR051XUqmFtkS4isTjAp2k32oafajkY_uUuoj8YBnrxU_dftpcUH29i7Mw1BTbK4NxhKeLFjqm10nr6NYGhW
- https://vertexaisearch.cloud.google.google.com/grounding-api-redirect/AUZIYQHRNd9BZn1MjHUOfraVN0M-lOmeR4MwY1H996MRmSgrMa_M8ltXq55koQy8mZMp8qX81g0hD0pej8RIRGqm-HQ78cpzS9bPETUM8U3widcctrJV17mCD3BL5ZIS0mnjPwiLCfEBFlBnnqmSbhWYAZRFDQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE51Z5ocXnoSUXungdkak5ZLsiKdaUX84ZeeP6j39JeZoqHktV_qniGaNiZjH2OuJHMZotm9r2tbGEcHyW5vyCFyfgswdDLAX_xOTwSqyDwt6zuSUIu6iPtljj5nTxKUA7qxw=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE-x2GA6iNiRLY7AMfOXiLjO5ihSQkMOvTrYMmrIzq5Fe-tVMqokLqp4E9fzK8BLwsWMJn9-Cz0Jm7rYbMDE0T5AcDSCj8iHcq5s7tdu0t4urdN6IbaDQurQ0eU9_x6mC62DtHJ4L-06g2RrCh632DdIg7h1OyN_bPc
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFSIlctIveQ1D4Rf1q6cg3hrR7WSNgI2prFiSjdGkaoxJDUiPjYLRwei8YX3pcdvrogHZ7Sv3lq_v5OptlVD1gzdODz9jgnLwbiGdMwlbnVjRqeE1Yg_EZks2LpiiwDydc9fFTBQvoW9mgFCzrEHSKwX3wIu3HSGiUQ_NTC3kOnE_aUTVXXYZGd4fwiA18f0babtvKzWhaFqLVJlByJD0nh9lYgCZfQz8C4Tos4rh8=