Source File
client.go
Belonging Package
net/http
// 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.// HTTP client. See RFC 7230 through 7235.//// This is the high-level Client interface.// The low-level implementation is in transport.go.package httpimport ()// A Client is an HTTP client. Its zero value (DefaultClient) is a// usable client that uses DefaultTransport.//// The Client's Transport typically has internal state (cached TCP// connections), so Clients should be reused instead of created as// needed. Clients are safe for concurrent use by multiple goroutines.//// A Client is higher-level than a RoundTripper (such as Transport)// and additionally handles HTTP details such as cookies and// redirects.//// When following redirects, the Client will forward all headers set on the// initial Request except://// • when forwarding sensitive headers like "Authorization",// "WWW-Authenticate", and "Cookie" to untrusted targets.// These headers will be ignored when following a redirect to a domain// that is not a subdomain match or exact match of the initial domain.// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"// will forward the sensitive headers, but a redirect to "bar.com" will not.//// • when forwarding the "Cookie" header with a non-nil cookie Jar.// Since each redirect may mutate the state of the cookie jar,// a redirect may possibly alter a cookie set in the initial request.// When forwarding the "Cookie" header, any mutated cookies will be omitted,// with the expectation that the Jar will insert those mutated cookies// with the updated values (assuming the origin matches).// If Jar is nil, the initial cookies are forwarded without change.//type Client struct {// Transport specifies the mechanism by which individual// HTTP requests are made.// If nil, DefaultTransport is used.Transport RoundTripper// CheckRedirect specifies the policy for handling redirects.// If CheckRedirect is not nil, the client calls it before// following an HTTP redirect. The arguments req and via are// the upcoming request and the requests made already, oldest// first. If CheckRedirect returns an error, the Client's Get// method returns both the previous Response (with its Body// closed) and CheckRedirect's error (wrapped in a url.Error)// instead of issuing the Request req.// As a special case, if CheckRedirect returns ErrUseLastResponse,// then the most recent response is returned with its body// unclosed, along with a nil error.//// If CheckRedirect is nil, the Client uses its default policy,// which is to stop after 10 consecutive requests.CheckRedirect func(req *Request, via []*Request) error// Jar specifies the cookie jar.//// The Jar is used to insert relevant cookies into every// outbound Request and is updated with the cookie values// of every inbound Response. The Jar is consulted for every// redirect that the Client follows.//// If Jar is nil, cookies are only sent if they are explicitly// set on the Request.Jar CookieJar// Timeout specifies a time limit for requests made by this// Client. The timeout includes connection time, any// redirects, and reading the response body. The timer remains// running after Get, Head, Post, or Do return and will// interrupt reading of the Response.Body.//// A Timeout of zero means no timeout.//// The Client cancels requests to the underlying Transport// as if the Request's Context ended.//// For compatibility, the Client will also use the deprecated// CancelRequest method on Transport if found. New// RoundTripper implementations should use the Request's Context// for cancellation instead of implementing CancelRequest.Timeout time.Duration}// DefaultClient is the default Client and is used by Get, Head, and Post.var DefaultClient = &Client{}// RoundTripper is an interface representing the ability to execute a// single HTTP transaction, obtaining the Response for a given Request.//// A RoundTripper must be safe for concurrent use by multiple// goroutines.type RoundTripper interface {// RoundTrip executes a single HTTP transaction, returning// a Response for the provided Request.//// RoundTrip should not attempt to interpret the response. In// particular, RoundTrip must return err == nil if it obtained// a response, regardless of the response's HTTP status code.// A non-nil err should be reserved for failure to obtain a// response. Similarly, RoundTrip should not attempt to// handle higher-level protocol details such as redirects,// authentication, or cookies.//// RoundTrip should not modify the request, except for// consuming and closing the Request's Body. RoundTrip may// read fields of the request in a separate goroutine. Callers// should not mutate or reuse the request until the Response's// Body has been closed.//// RoundTrip must always close the body, including on errors,// but depending on the implementation may do so in a separate// goroutine even after RoundTrip returns. This means that// callers wanting to reuse the body for subsequent requests// must arrange to wait for the Close call before doing so.//// The Request's URL and Header fields must be initialized.RoundTrip(*Request) (*Response, error)}// refererForURL returns a referer without any authentication info or// an empty string if lastReq scheme is https and newReq scheme is http.func (, *url.URL) string {// https://tools.ietf.org/html/rfc7231#section-5.5.2// "Clients SHOULD NOT include a Referer header field in a// (non-secure) HTTP request if the referring page was// transferred with a secure protocol."if .Scheme == "https" && .Scheme == "http" {return ""}:= .String()if .User != nil {// This is not very efficient, but is the best we can// do without:// - introducing a new method on URL// - creating a race condition// - copying the URL struct manually, which would cause// maintenance problems down the line:= .User.String() + "@"= strings.Replace(, , "", 1)}return}// didTimeout is non-nil only if err != nil.func ( *Client) ( *Request, time.Time) ( *Response, func() bool, error) {if .Jar != nil {for , := range .Jar.Cookies(.URL) {.AddCookie()}}, , = send(, .transport(), )if != nil {return nil, ,}if .Jar != nil {if := .Cookies(); len() > 0 {.Jar.SetCookies(.URL, )}}return , nil, nil}func ( *Client) () time.Time {if .Timeout > 0 {return time.Now().Add(.Timeout)}return time.Time{}}func ( *Client) () RoundTripper {if .Transport != nil {return .Transport}return DefaultTransport}// send issues an HTTP request.// Caller should close resp.Body when done reading from it.func ( *Request, RoundTripper, time.Time) ( *Response, func() bool, error) {:= // req is either the original request, or a modified forkif == nil {.closeBody()return nil, alwaysFalse, errors.New("http: no Client.Transport or DefaultTransport")}if .URL == nil {.closeBody()return nil, alwaysFalse, errors.New("http: nil Request.URL")}if .RequestURI != "" {.closeBody()return nil, alwaysFalse, errors.New("http: Request.RequestURI can't be set in client requests")}// forkReq forks req into a shallow clone of ireq the first// time it's called.:= func() {if == {= new(Request)* = * // shallow clone}}// Most the callers of send (Get, Post, et al) don't need// Headers, leaving it uninitialized. We guarantee to the// Transport that this has been initialized, though.if .Header == nil {().Header = make(Header)}if := .URL.User; != nil && .Header.Get("Authorization") == "" {:= .Username(), := .Password()().Header = cloneOrMakeHeader(.Header).Header.Set("Authorization", "Basic "+basicAuth(, ))}if !.IsZero() {()}, := setRequestCancel(, , ), = .RoundTrip()if != nil {()if != nil {log.Printf("RoundTripper returned a response & error; ignoring response")}if , := .(tls.RecordHeaderError); {// If we get a bad TLS record header, check to see if the// response looks like HTTP and give a more helpful error.// See golang.org/issue/11111.if string(.RecordHeader[:]) == "HTTP/" {= errors.New("http: server gave HTTP response to HTTPS client")}}return nil, ,}if == nil {return nil, , fmt.Errorf("http: RoundTripper implementation (%T) returned a nil *Response with a nil error", )}if .Body == nil {// The documentation on the Body field says “The http Client and Transport// guarantee that Body is always non-nil, even on responses without a body// or responses with a zero-length body.” Unfortunately, we didn't document// that same constraint for arbitrary RoundTripper implementations, and// RoundTripper implementations in the wild (mostly in tests) assume that// they can use a nil Body to mean an empty one (similar to Request.Body).// (See https://golang.org/issue/38095.)//// If the ContentLength allows the Body to be empty, fill in an empty one// here to ensure that it is non-nil.if .ContentLength > 0 && .Method != "HEAD" {return nil, , fmt.Errorf("http: RoundTripper implementation (%T) returned a *Response with content length %d but a nil Body", , .ContentLength)}.Body = io.NopCloser(strings.NewReader(""))}if !.IsZero() {.Body = &cancelTimerBody{stop: ,rc: .Body,reqDidTimeout: ,}}return , nil, nil}// timeBeforeContextDeadline reports whether the non-zero Time t is// before ctx's deadline, if any. If ctx does not have a deadline, it// always reports true (the deadline is considered infinite).func ( time.Time, context.Context) bool {, := .Deadline()if ! {return true}return .Before()}// knownRoundTripperImpl reports whether rt is a RoundTripper that's// maintained by the Go team and known to implement the latest// optional semantics (notably contexts). The Request is used// to check whether this particular request is using an alternate protocol,// in which case we need to check the RoundTripper for that protocol.func ( RoundTripper, *Request) bool {switch t := .(type) {case *Transport:if := .alternateRoundTripper(); != nil {return (, )}return truecase *http2Transport, http2noDialH2RoundTripper:return true}// There's a very minor chance of a false positive with this.// Instead of detecting our golang.org/x/net/http2.Transport,// it might detect a Transport type in a different http2// package. But I know of none, and the only problem would be// some temporarily leaked goroutines if the transport didn't// support contexts. So this is a good enough heuristic:if reflect.TypeOf().String() == "*http2.Transport" {return true}return false}// setRequestCancel sets req.Cancel and adds a deadline context to req// if deadline is non-zero. The RoundTripper's type is used to// determine whether the legacy CancelRequest behavior should be used.//// As background, there are three ways to cancel a request:// First was Transport.CancelRequest. (deprecated)// Second was Request.Cancel.// Third was Request.Context.// This function populates the second and third, and uses the first if it really needs to.func ( *Request, RoundTripper, time.Time) ( func(), func() bool) {if .IsZero() {return nop, alwaysFalse}:= knownRoundTripperImpl(, ):= .Context()if .Cancel == nil && {// If they already had a Request.Context that's// expiring sooner, do nothing:if !timeBeforeContextDeadline(, ) {return nop, alwaysFalse}var func().ctx, = context.WithDeadline(, )return , func() bool { return time.Now().After() }}:= .Cancel // the user's original Request.Cancel, if anyvar func()if := .Context(); timeBeforeContextDeadline(, ) {.ctx, = context.WithDeadline(, )}:= make(chan struct{}).Cancel =:= func() {// The second way in the func comment above:close()// The first way, used only for RoundTripper// implementations written before Go 1.5 or Go 1.6.type interface{ (*Request) }if , := .(); {.()}}:= make(chan struct{})var sync.Once= func() {.Do(func() {close()if != nil {()}})}:= time.NewTimer(time.Until())var atomicBoolgo func() {select {case <-:().Stop()case <-.C:.setTrue()()case <-:.Stop()}}()return , .isSet}// See 2 (end of page 4) https://www.ietf.org/rfc/rfc2617.txt// "To receive authorization, the client sends the userid and password,// separated by a single colon (":") character, within a base64// encoded string in the credentials."// It is not meant to be urlencoded.func (, string) string {:= + ":" +return base64.StdEncoding.EncodeToString([]byte())}// Get issues a GET to the specified URL. If the response is one of// the following redirect codes, Get follows the redirect, up to a// maximum of 10 redirects://// 301 (Moved Permanently)// 302 (Found)// 303 (See Other)// 307 (Temporary Redirect)// 308 (Permanent Redirect)//// An error is returned if there were too many redirects or if there// was an HTTP protocol error. A non-2xx response doesn't cause an// error. Any returned error will be of type *url.Error. The url.Error// value's Timeout method will report true if request timed out or was// canceled.//// When err is nil, resp always contains a non-nil resp.Body.// Caller should close resp.Body when done reading from it.//// Get is a wrapper around DefaultClient.Get.//// To make a request with custom headers, use NewRequest and// DefaultClient.Do.func ( string) ( *Response, error) {return DefaultClient.Get()}// Get issues a GET to the specified URL. If the response is one of the// following redirect codes, Get follows the redirect after calling the// Client's CheckRedirect function://// 301 (Moved Permanently)// 302 (Found)// 303 (See Other)// 307 (Temporary Redirect)// 308 (Permanent Redirect)//// An error is returned if the Client's CheckRedirect function fails// or if there was an HTTP protocol error. A non-2xx response doesn't// cause an error. Any returned error will be of type *url.Error. The// url.Error value's Timeout method will report true if the request// timed out.//// When err is nil, resp always contains a non-nil resp.Body.// Caller should close resp.Body when done reading from it.//// To make a request with custom headers, use NewRequest and Client.Do.func ( *Client) ( string) ( *Response, error) {, := NewRequest("GET", , nil)if != nil {return nil,}return .Do()}func () bool { return false }// ErrUseLastResponse can be returned by Client.CheckRedirect hooks to// control how redirects are processed. If returned, the next request// is not sent and the most recent response is returned with its body// unclosed.var ErrUseLastResponse = errors.New("net/http: use last response")// checkRedirect calls either the user's configured CheckRedirect// function, or the default.func ( *Client) ( *Request, []*Request) error {:= .CheckRedirectif == nil {= defaultCheckRedirect}return (, )}// redirectBehavior describes what should happen when the// client encounters a 3xx status code from the serverfunc ( string, *Response, *Request) ( string, , bool) {switch .StatusCode {case 301, 302, 303:== true= false// RFC 2616 allowed automatic redirection only with GET and// HEAD requests. RFC 7231 lifts this restriction, but we still// restrict other methods to GET to maintain compatibility.// See Issue 18570.if != "GET" && != "HEAD" {= "GET"}case 307, 308:== true= true// Treat 307 and 308 specially, since they're new in// Go 1.8, and they also require re-sending the request body.if .Header.Get("Location") == "" {// 308s have been observed in the wild being served// without Location headers. Since Go 1.7 and earlier// didn't follow these codes, just stop here instead// of returning an error.// See Issue 17773.= falsebreak}if .GetBody == nil && .outgoingLength() != 0 {// We had a request body, and 307/308 require// re-sending it, but GetBody is not defined. So just// return this response to the user instead of an// error, like we did in Go 1.7 and earlier.= false}}return , ,}// urlErrorOp returns the (*url.Error).Op value to use for the// provided (*Request).Method value.func ( string) string {if == "" {return "Get"}return [:1] + strings.ToLower([1:])}// Do sends an HTTP request and returns an HTTP response, following// policy (such as redirects, cookies, auth) as configured on the// client.//// An error is returned if caused by client policy (such as// CheckRedirect), or failure to speak HTTP (such as a network// connectivity problem). A non-2xx status code doesn't cause an// error.//// If the returned error is nil, the Response will contain a non-nil// Body which the user is expected to close. If the Body is not both// read to EOF and closed, the Client's underlying RoundTripper// (typically Transport) may not be able to re-use a persistent TCP// connection to the server for a subsequent "keep-alive" request.//// The request Body, if non-nil, will be closed by the underlying// Transport, even on errors.//// On error, any Response can be ignored. A non-nil Response with a// non-nil error only occurs when CheckRedirect fails, and even then// the returned Response.Body is already closed.//// Generally Get, Post, or PostForm will be used instead of Do.//// If the server replies with a redirect, the Client first uses the// CheckRedirect function to determine whether the redirect should be// followed. If permitted, a 301, 302, or 303 redirect causes// subsequent requests to use HTTP method GET// (or HEAD if the original request was HEAD), with no body.// A 307 or 308 redirect preserves the original HTTP method and body,// provided that the Request.GetBody function is defined.// The NewRequest function automatically sets GetBody for common// standard library body types.//// Any returned error will be of type *url.Error. The url.Error// value's Timeout method will report true if request timed out or was// canceled.func ( *Client) ( *Request) (*Response, error) {return .do()}var testHookClientDoResult func(retres *Response, reterr error)func ( *Client) ( *Request) ( *Response, error) {if testHookClientDoResult != nil {defer func() { testHookClientDoResult(, ) }()}if .URL == nil {.closeBody()return nil, &url.Error{Op: urlErrorOp(.Method),Err: errors.New("http: nil Request.URL"),}}var (= .deadline()[]*Request*Response= .makeHeadersCopier()= false // have we closed the current req.Body?// Redirect behavior:stringbool):= func( error) error {// the body may have been closed already by c.send()if ! {.closeBody()}var stringif != nil && .Request != nil {= stripPassword(.Request.URL)} else {= stripPassword(.URL)}return &url.Error{Op: urlErrorOp([0].Method),URL: ,Err: ,}}for {// For all but the first request, create the next// request hop and replace req.if len() > 0 {:= .Header.Get("Location")if == "" {.closeBody()return nil, (fmt.Errorf("%d response missing Location header", .StatusCode))}, := .URL.Parse()if != nil {.closeBody()return nil, (fmt.Errorf("failed to parse Location header %q: %v", , ))}:= ""if .Host != "" && .Host != .URL.Host {// If the caller specified a custom Host header and the// redirect location is relative, preserve the Host header// through the redirect. See issue #22233.if , := url.Parse(); != nil && !.IsAbs() {= .Host}}:= [0]= &Request{Method: ,Response: ,URL: ,Header: make(Header),Host: ,Cancel: .Cancel,ctx: .ctx,}if && .GetBody != nil {.Body, = .GetBody()if != nil {.closeBody()return nil, ()}.ContentLength = .ContentLength}// Copy original headers before setting the Referer,// in case the user set Referer on their first request.// If they really want to override, they can do it in// their CheckRedirect func.()// Add the Referer header from the most recent// request URL to the new one, if it's not https->http:if := refererForURL([len()-1].URL, .URL); != "" {.Header.Set("Referer", )}= .checkRedirect(, )// Sentinel error to let users select the// previous response, without closing its// body. See Issue 10069.if == ErrUseLastResponse {return , nil}// Close the previous response's body. But// read at least some of the body so if it's// small the underlying TCP connection will be// re-used. No need to check for errors: if it// fails, the Transport won't reuse it anyway.const = 2 << 10if .ContentLength == -1 || .ContentLength <= {io.CopyN(io.Discard, .Body, )}.Body.Close()if != nil {// Special case for Go 1 compatibility: return both the response// and an error if the CheckRedirect function failed.// See https://golang.org/issue/3795// The resp.Body has already been closed.:= ().(*url.Error).URL =return ,}}= append(, )var errorvar func() boolif , , = .send(, ); != nil {// c.send() always closes req.Body= trueif !.IsZero() && () {= &httpError{// TODO: early in cycle: s/Client.Timeout exceeded/timeout or context cancellation/err: .Error() + " (Client.Timeout exceeded while awaiting headers)",timeout: true,}}return nil, ()}var bool, , = redirectBehavior(.Method, , [0])if ! {return , nil}.closeBody()}}// makeHeadersCopier makes a function that copies headers from the// initial Request, ireq. For every redirect, this function must be called// so that it can copy headers into the upcoming Request.func ( *Client) ( *Request) func(*Request) {// The headers to copy are from the very initial request.// We use a closured callback to keep a reference to these original headers.var (= cloneOrMakeHeader(.Header)map[string][]*Cookie)if .Jar != nil && .Header.Get("Cookie") != "" {= make(map[string][]*Cookie)for , := range .Cookies() {[.Name] = append([.Name], )}}:= // The previous requestreturn func( *Request) {// If Jar is present and there was some initial cookies provided// via the request header, then we may need to alter the initial// cookies as we follow redirects since each redirect may end up// modifying a pre-existing cookie.//// Since cookies already set in the request header do not contain// information about the original domain and path, the logic below// assumes any new set cookies override the original cookie// regardless of domain or path.//// See https://golang.org/issue/17494if .Jar != nil && != nil {var bool:= .Response // The response that caused the upcoming redirectfor , := range .Cookies() {if , := [.Name]; {delete(, .Name)= true}}if {.Del("Cookie")var []stringfor , := range {for , := range {= append(, .Name+"="+.Value)}}sort.Strings() // Ensure deterministic headers.Set("Cookie", strings.Join(, "; "))}}// Copy the initial request's Header values// (at least the safe ones).for , := range {if shouldCopyHeaderOnRedirect(, .URL, .URL) {.Header[] =}}= // Update previous Request with the current request}}func ( *Request, []*Request) error {if len() >= 10 {return errors.New("stopped after 10 redirects")}return nil}// Post issues a POST to the specified URL.//// Caller should close resp.Body when done reading from it.//// If the provided body is an io.Closer, it is closed after the// request.//// Post is a wrapper around DefaultClient.Post.//// To set custom headers, use NewRequest and DefaultClient.Do.//// See the Client.Do method documentation for details on how redirects// are handled.func (, string, io.Reader) ( *Response, error) {return DefaultClient.Post(, , )}// Post issues a POST to the specified URL.//// Caller should close resp.Body when done reading from it.//// If the provided body is an io.Closer, it is closed after the// request.//// To set custom headers, use NewRequest and Client.Do.//// See the Client.Do method documentation for details on how redirects// are handled.func ( *Client) (, string, io.Reader) ( *Response, error) {, := NewRequest("POST", , )if != nil {return nil,}.Header.Set("Content-Type", )return .Do()}// PostForm issues a POST to the specified URL, with data's keys and// values URL-encoded as the request body.//// The Content-Type header is set to application/x-www-form-urlencoded.// To set other headers, use NewRequest and DefaultClient.Do.//// When err is nil, resp always contains a non-nil resp.Body.// Caller should close resp.Body when done reading from it.//// PostForm is a wrapper around DefaultClient.PostForm.//// See the Client.Do method documentation for details on how redirects// are handled.func ( string, url.Values) ( *Response, error) {return DefaultClient.PostForm(, )}// PostForm issues a POST to the specified URL,// with data's keys and values URL-encoded as the request body.//// The Content-Type header is set to application/x-www-form-urlencoded.// To set other headers, use NewRequest and Client.Do.//// When err is nil, resp always contains a non-nil resp.Body.// Caller should close resp.Body when done reading from it.//// See the Client.Do method documentation for details on how redirects// are handled.func ( *Client) ( string, url.Values) ( *Response, error) {return .Post(, "application/x-www-form-urlencoded", strings.NewReader(.Encode()))}// Head issues a HEAD to the specified URL. If the response is one of// the following redirect codes, Head follows the redirect, up to a// maximum of 10 redirects://// 301 (Moved Permanently)// 302 (Found)// 303 (See Other)// 307 (Temporary Redirect)// 308 (Permanent Redirect)//// Head is a wrapper around DefaultClient.Headfunc ( string) ( *Response, error) {return DefaultClient.Head()}// Head issues a HEAD to the specified URL. If the response is one of the// following redirect codes, Head follows the redirect after calling the// Client's CheckRedirect function://// 301 (Moved Permanently)// 302 (Found)// 303 (See Other)// 307 (Temporary Redirect)// 308 (Permanent Redirect)func ( *Client) ( string) ( *Response, error) {, := NewRequest("HEAD", , nil)if != nil {return nil,}return .Do()}// CloseIdleConnections closes any connections on its Transport which// were previously connected from previous requests but are now// sitting idle in a "keep-alive" state. It does not interrupt any// connections currently in use.//// If the Client's Transport does not have a CloseIdleConnections method// then this method does nothing.func ( *Client) () {type interface {()}if , := .transport().(); {.()}}// cancelTimerBody is an io.ReadCloser that wraps rc with two features:// 1) on Read error or close, the stop func is called.// 2) On Read failure, if reqDidTimeout is true, the error is wrapped and// marked as net.Error that hit its timeout.type cancelTimerBody struct {stop func() // stops the time.Timer waiting to cancel the requestrc io.ReadCloserreqDidTimeout func() bool}func ( *cancelTimerBody) ( []byte) ( int, error) {, = .rc.Read()if == nil {return , nil}.stop()if == io.EOF {return ,}if .reqDidTimeout() {= &httpError{err: .Error() + " (Client.Timeout or context cancellation while reading body)",timeout: true,}}return ,}func ( *cancelTimerBody) () error {:= .rc.Close().stop()return}func ( string, , *url.URL) bool {switch CanonicalHeaderKey() {case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":// Permit sending auth/cookie headers from "foo.com"// to "sub.foo.com".// Note that we don't send all cookies to subdomains// automatically. This function is only used for// Cookies set explicitly on the initial outgoing// client request. Cookies automatically added via the// CookieJar mechanism continue to follow each// cookie's scope as set by Set-Cookie. But for// outgoing requests with the Cookie header set// directly, we don't know their scope, so we assume// it's for *.domain.com.:= canonicalAddr():= canonicalAddr()return isDomainOrSubdomain(, )}// All other headers are copied:return true}// isDomainOrSubdomain reports whether sub is a subdomain (or exact// match) of the parent domain.//// Both domains must already be in canonical form.func (, string) bool {if == {return true}// If sub is "foo.example.com" and parent is "example.com",// that means sub must end in "."+parent.// Do it without allocating.if !strings.HasSuffix(, ) {return false}return [len()-len()-1] == '.'}func ( *url.URL) string {, := .User.Password()if {return strings.Replace(.String(), .User.String()+"@", .User.Username()+":***@", 1)}return .String()}