Skip to content

Shelf Response: Reason Phrase is not customizable #449

Open
@JesusHdez960717

Description

@JesusHdez960717

When we return a response with a status like 422, the reason phrase is empty and no details are specified.

This is the example to reproduce it:

import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;

void main() async {
  var handler =
      const Pipeline().addMiddleware(logRequests()).addHandler(_echoRequest);

  var server = await shelf_io.serve(handler, 'localhost', 8080);

  // Enable content compression
  server.autoCompress = true;

  print('Serving at http://${server.address.host}:${server.port}');
}

Response _echoRequest(Request request) => Response(422);

When making the request in postman it looks like this:
image

When we create the same service in other framework, like spring for example, the response is more explained like this.

Having this example:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @RestController
    public static class TestController {
        @GetMapping("/")
        public ResponseEntity close() {
            return new ResponseEntity(HttpStatusCode.valueOf(422));
        }
    }
}

This give us a more explained response like this:

image

Looking a little deeper, the HttpResponse class (from the http lib), has the reason phrase parameter to be set. However the Response class (from shelf) does not have this property to be configured. And of course, in the _writeResponse of the shelf_io the mapping to said property is not done.

Fixing this is as easy as adding the property 'reasonPhrase' to the Response and later mapping it to HttpResponse:

/// The response returned by a [Handler].
class Response extends Message {
  /// The HTTP status code of the response.
  final int statusCode;

  /// THIS PROPERTY IS THE ONE TO ADD
  /// The reason phrase for the response.
  ///
  /// If no reason phrase is explicitly set, a default reason phrase is provided.
  final String? reasonPhrase;
  
 ///(AL THE OTHER STUFF IN THE CLASS)

  /// Constructs an HTTP response with the given [statusCode].
  ///
  /// [statusCode] must be greater than or equal to 100.
  ///
  /// {@macro shelf_response_body_and_encoding_param}
  Response(
    this.statusCode, {
    this.reasonPhrase, //ADDED reason phrase param
    Object? body,
    Map<String, /* String | List<String> */ Object>? headers,
    Encoding? encoding,
    Map<String, Object>? context,
  }) : super(body, encoding: encoding, headers: headers, context: context) {
    if (statusCode < 100) {
      throw ArgumentError('Invalid status code: $statusCode.');
    }
  }

And also we need to added in write function:

Future<void> _writeResponse(
    Response response, HttpResponse httpResponse, String? poweredByHeader) {
  if (response.context.containsKey('shelf.io.buffer_output')) {
    httpResponse.bufferOutput =
        response.context['shelf.io.buffer_output'] as bool;
  }

  httpResponse.statusCode = response.statusCode;
  httpResponse.reasonPhrase = response.reasonPhrase ?? ''; ///NEW LINE TO ADD IN ORDER TO SET UP PROPERTY

  // An adapter must not add or modify the `Transfer-Encoding` parameter, but
  // the Dart SDK sets it by default. Set this before we fill in
  // [response.headers] so that the user or Shelf can explicitly override it if
  // necessary.
  httpResponse.headers.chunkedTransferEncoding = false;

  (MORE STUFF OF METHOD)

Whit this couple of changes when we run the same example it give us the following response:
image

This way we have a better explanation of the response codes.

This solution clearly don't affect others status codes:

image
image
image

Would this be a worthy feature to add to the package to create a PR?
Or was it designed this way specifically for some reason?

Thanks in advance

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions