Чому в обробниках HTTP Go, чому ResponseWriter є значенням, а Request не вказівником?


84

Я вивчаю Go, написавши програму для GAE, і це підпис функції обробника:

func handle(w http.ResponseWriter, r *http.Request) {}

Я тут новачок вказівника, то чому Requestоб’єкт є вказівником, а ResponseWriterні? Чи є необхідність мати його таким чином, чи це просто для того, щоб зробити можливим якийсь вдосконалений код на основі вказівника?

Відповіді:


66

Ви отримуєте wвказівник на не експортований тип, http.responseале як ResponseWriterі інтерфейс, цього не видно.

З сервера.go :

type ResponseWriter interface {
    ...
}

З іншого боку, rце вказівник на конкретну структуру, отже, необхідність явно передавати посилання.

З request.go :

type Request struct {
    ...
}

28

Це http.ResponseWriterінтерфейс, а існуючі типи, що реалізують цей інтерфейс, є покажчиками. Це означає, що немає необхідності використовувати вказівник на цей інтерфейс, оскільки він уже «підкріплений» вказівником. Цю концепцію трохи описує один із розробників go тут Хоча типу, що реалізує http.ResponseWriter, не потрібно було бути вказівником, це не було б практично, принаймні не в межах сервера http go.

http.Requestце не інтерфейс, це просто структура, і оскільки ми хочемо змінити цю структуру і веб-сервер побачить ці зміни, це повинен бути вказівник. Якби це було просто значення структури, ми б просто модифікували його копію, яку веб-сервер, що викликає наш код, не міг бачити.


1
Я вважаю, що це не правильно. Значення, що стоять за вказівниками, можуть реалізовувати такі ж інтерфейси, як і значення, тому тут немає необхідності розрізняти. Тип з базовим типом int може задовольнити інтерфейс, не будучи покажчиком або не підтримуючись ним.
nemo

2
Ну, я не мав на увазі, що всі речі, що реалізують інтерфейс, повинні бути вказівниками. Щось на зразок ResponseWriter, якому потрібно буде змінити стан значення, що стоїть за інтерфейсом, щоб зробити щось корисне, і що вимагатиме, щоб це значення було типом вказівника (як також пише допис у блозі, до якого я посилався). Але так, http.ResponseWriter теоретично може бути реалізований int (методи якого ніколи не можуть змінити це значення int).
No

Ця відповідь була дуже корисною. Це посилання є безцінним, з чітким поясненням для тих, хто новачок у нас. "Тобто, хоча немає явного маркера, об'єкти інтерфейсу часто діють як покажчики. Це може заплутати, поки ви не зрозумієте, що насправді відбувається".
Cody Django

1
Частина про запит насправді не так, побачити моя відповідь stackoverflow.com/a/56875204/989991
Йоаким

7

Як правильно згадувалось у багатьох інших відповідях тут та в інших місцях, ResponseWriterце інтерфейс, і наслідки цього були детально описані у відповідях та блогах SO .

Я хотів би звернутися до того, що, на мою думку, тут є велике і небезпечне помилкове уявлення про те, що запит про причину передається за допомогою "посилання" (хоча такого в насправді не існує в Go ) полягає в тому, що "ми хочемо внести зміни йому видиме серверу ".

Процитувавши пару відповідей:

[..] це просто структура, і оскільки ми хочемо змінити цю структуру і щоб веб-сервер бачив ці зміни, це повинен бути вказівник [..] ТАК

[..] зміни в Запит обробником повинні бути видимими для сервера, тому ми передаємо їх лише за посиланням, а не за значенням [..] SO

Це неправильно ; насправді документи прямо застерігають від фальсифікації / мутації запиту :

За винятком читання тексту, обробники не повинні змінювати наданий Запит.

Зовсім навпаки, ні? :-)

Якщо ви хочете змінити запит, наприклад, додати заголовок трасування, перш ніж передавати його наступному обробнику в ланцюжку проміжного програмного забезпечення, вам потрібно скопіювати запит і передати скопійовану версію вниз по ланцюжку.

Запити на зміну поведінки, щоб дозволити модифікації вхідного запиту , були порушені командою Go, але зміна чогось подібного, можливо, призвело б до того, що принаймні якийсь існуючий код несподівано зламався.

Навіщо використовувати вказівник, якщо ми прямо говоримо людям, щоб не мутували запит? Продуктивність , Requestвелика структура і копіювання може принести продуктивність вниз, особливо з довгими ланцюгами проміжного програмного забезпечення на увазі. Команді довелося знайти рівновагу, безумовно, не ідеальне рішення, але компроміси тут явно на стороні ефективності (замість безпеки API).


1
Це відповідь, яка нарешті мала для мене сенс.
Джимі

2

Причина, по якій це вказівник на Request, проста: зміни в Request за допомогою обробника повинні бути видимими для сервера, тому ми передаємо його лише за посиланням, а не за значенням.

Якщо заглибитися в код бібліотеки net / http, ви виявите, що ResponseWriter є інтерфейсом до неекспортованої відповіді структури, і ми передаємо структуру за посиланням (ми передаємо вказівник на відповідь), а не за значенням . ResponseWriter - це інтерфейс, який обробник використовує для створення відповіді HTTP. Фактичною резервною копією структури ResponseWriter є неекспортована структура http.response. Оскільки він не експортується, ви не можете використовувати його безпосередньо; Ви можете використовувати його лише через інтерфейс ResponseWriter.

Іншими словами, обидва параметри передаються за посиланням; просто підпис методу приймає ResponseWriter, який є інтерфейсом до вказівника на структуру, тому виглядає так, ніби він переданий за значенням.


Це має більше сенсу, ніж оригінальна відповідь для мене
Венката ССКМ Чайтанья

Частина про Запит насправді неправильна, див. Мою відповідь
stackoverflow.com/a/56875204/989991

0

Я думаю, що основною причиною Requestпередачі об’єкта як покажчика є Bodyполе. Для даного запиту HTTP тіло можемо прочитати лише один раз. Якби Requestоб’єкт було клоновано, як це було б, якби його не передали як вказівник, ми мали б два об’єкти з різною інформацією про те, скільки прочитано з тіла.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.