множествен отговор.WriteHeader извиквания в наистина прост пример?

Имам най-основната net/http програма, която използвам, за да науча пространството от имена в Go:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(r.URL)
        go HandleIndex(w, r)
    })

    fmt.Println("Starting Server...")
    log.Fatal(http.ListenAndServe(":5678", nil))
}

func HandleIndex(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Write([]byte("Hello, World!"))
}

Когато стартирам програмата и се свържа с localhost:5678 в Chrome, получавам това в конзолата:

Starting Server...
/
2015/01/15 13:41:29 http: multiple response.WriteHeader calls
/favicon.ico
2015/01/15 13:41:29 http: multiple response.WriteHeader calls

Но не виждам как е възможно това. Отпечатвам URL адреса, стартирам нова goroutine, записвам заглавката веднъж и му давам статично тяло от Hello, World! Изглежда, че се случва едно от двете неща. Или нещо зад кулисите пише друго заглавие, или по някакъв начин HandleIndex се извиква два пъти за една и съща заявка. Какво мога да направя, за да спра да пиша множество заглавки?

РЕДАКТИРАНЕ: Изглежда има нещо общо с реда go HandleIndex(w, r), защото ако премахна go и просто го направя извикване на функция вместо goroutine, не получавам никакви проблеми и браузърът получава неговите данни. Тъй като е goroutine, получавам множествената грешка WriteHeader и браузърът не показва „Hello World“. Защо превръщането на това в goroutine го нарушава?


person Corey Ogburn    schedule 15.01.2015    source източник


Отговори (6)


Разгледайте анонимната функция, която регистрирате като манипулатор на входящи заявки:

func(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.URL)
    go HandleIndex(w, r)
}

Той отпечатва URL адреса (към стандартния изход), след което извиква HandleIndex() в нова goroutine и продължава изпълнението.

Ако имате манипулираща функция, при която не задавате статуса на отговор преди първото извикване на Write, Go автоматично ще зададе статуса на отговор на 200 (HTTP OK). Ако манипулаторната функция не напише нищо в отговора (и не зададе статуса на отговора и завърши нормално), това също се третира като успешно обработване на заявката и статусът на отговор 200 ще бъде изпратен обратно. Вашата анонимна функция не го задава, дори не пише нищо в отговора. Така че Go ще направи точно това: задайте състоянието на отговора на 200 HTTP OK.

Обърнете внимание, че обработката на всяка заявка се изпълнява в собствена горограма.

Така че, ако извикате HandleIndex в нова goroutine, вашата оригинална анонимна функция ще продължи: тя ще приключи и така ще бъде зададена заглавката на отговора - междувременно (едновременно) вашата стартирана нова goroutine също ще зададе заглавката на отговора - оттук и грешката "multiple response.WriteHeader calls".

Ако премахнете "go", вашата HandleIndex функция ще зададе заглавката на отговора в същата goroutine, преди вашата манипулираща функция да се върне, а "net/http" ще знае за това и няма да се опита да зададе заглавката на отговора отново, така че грешката, която опитен няма да стане.

person icza    schedule 15.01.2015
comment
Имайте предвид, че обработката на всяка заявка се изпълнява в собствена gorouting. Надявах се, че го направи, но никога не видях нещо, което да казва по един или друг начин дали това е вярно. - person Corey Ogburn; 16.01.2015
comment
@CoreyOgburn Цитирам от документацията на http.Serve(): "Serve accepts incoming HTTP connections on the listener l, creating a new service goroutine for each." - person icza; 16.01.2015

Вече получихте правилен отговор, който адресира вашия проблем, ще дам малко информация за общия случай (такава грешка се появява често).

От документацията виждате, че WriteHeader изпраща http код на състояние и не можете изпрати повече от 1 код на състояние. Ако Write нещо, това е еквивалентно на изпращане на 200 код за състояние и след това писане на неща.

Така че съобщението, което виждате, се появява, ако или използвате w.WriteHeader повече от веднъж изрично, или използвате w.Write преди w.WriteHeader.

person Salvador Dali    schedule 12.06.2016

От документацията:

// WriteHeader sends an HTTP response header with status code. 
// If WriteHeader is not called explicitly, the first call to Write  
// will trigger an implicit WriteHeader(http.StatusOK).

Това, което се случва във вашия случай е, че стартирате go HandleIndex от манипулатора. Първият манипулатор завършва. Стандартният WriteHeader пише в ResponseWriter. След това се стартира рутинната процедура HandleIndex и тя също се опитва да напише заглавка и да напише.

Просто премахнете go от HandleIndex и ще работи.

person fabrizioM    schedule 15.01.2015

основната причина е, че сте извикали WriteHeader повече от веднъж. от изходните кодове

func (w *response) WriteHeader(code int) {
    if w.conn.hijacked() {
        w.conn.server.logf("http: response.WriteHeader on hijacked connection")
        return
    }
    if w.wroteHeader {
        w.conn.server.logf("http: multiple response.WriteHeader calls")
        return
    }
    w.wroteHeader = true
    w.status = code

    if w.calledHeader && w.cw.header == nil {
        w.cw.header = w.handlerHeader.clone()
    }

    if cl := w.handlerHeader.get("Content-Length"); cl != "" {
        v, err := strconv.ParseInt(cl, 10, 64)
        if err == nil && v >= 0 {
            w.contentLength = v
        } else {
            w.conn.server.logf("http: invalid Content-Length of %q", cl)
            w.handlerHeader.Del("Content-Length")
        }
    }
}

така че когато сте написали веднъж, променлива writeHeader ще бъде вярна, след това сте написали заглавка отново, няма да е ефективна и ще даде предупреждение "http: множество respnse.WriteHeader извиквания". всъщност функцията Write също извиква WriteHeader, така че поставянето на функцията WriteHeader след функцията Write също причинява тази грешка и по-късният WriteHeader не работи.

от вашия случай, go handleindex се изпълнява в друга нишка и оригиналът вече се връща, ако не направите нищо, той ще извика WriteHeader, за да зададе 200. когато стартира handleindex, той извиква друг WriteHeader, по това време wroteHeader е верен, след това съобщението "http : множествен отговор.WriteHeader calls" се извежда.

person user7402259    schedule 11.01.2017

Да, използването на HandleIndex(w, r) вместо go HandleIndex(w, r) ще реши проблема ви, мисля, че вече сте разбрали това.

Причината е проста, когато обработва множество заявки едновременно, http сървърът ще стартира множество goroutines и вашата манипулираща функция ще бъде извикана отделно във всяка от goroutines, без да блокира други. Не е необходимо да стартирате своя собствена goroutine в манипулатора, освен ако практически не ви е необходима, но това ще бъде друга тема.

person Andy Xu    schedule 16.01.2015

Тъй като съвременните браузъри изпращат допълнителна заявка за /favicon.ico, която също се обработва от вашия / заявка за обработка.

Ако пингвате вашия сървър с curl например, ще видите само една изпратена заявка:

 curl localhost:5678

За да сте сигурни, можете да добавите EndPoint във вашия http.HandleFunc

http.HandleFunc("/Home", func(w http.ResponseWriter, r *http.Request) 
person abdel    schedule 14.07.2015