Skip to content

Support application/x-protobuf errors #2827

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rwinch opened this issue Apr 15, 2015 · 2 comments
Closed

Support application/x-protobuf errors #2827

rwinch opened this issue Apr 15, 2015 · 2 comments
Labels
for: external-project For an external project and not something we can fix

Comments

@rwinch
Copy link
Member

rwinch commented Apr 15, 2015

Spring 4.1 added support for protobuf. However, if an error occurs when the Accept header is set to "application/x-protobuf", then the response that is sent is a HTTP 406 Not Acceptable. For example, if a user requests a resource that requires authentication and no credentials are provided, then instead of an HTTP 401, the following response is sent:

HTTP/1.1 406 Not Acceptable
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Basic realm="Realm"
Content-Length: 0
Date: Wed, 15 Apr 2015 13:43:21 GMT

instead the error should look something like:

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Basic realm="Realm"
X-Protobuf-Schema: customer.proto
X-Protobuf-Message: demo.Error
Content-Type: application/x-protobuf;charset=UTF-8
Content-Length: 160
Date: Wed, 15 Apr 2015 14:23:33 GMT

...

This happens because BasicErrorController is creating an ResponseEntity<Map<String, Object>>. When ProtobufHttpMessageConverter attempts to write the body, it cannot because ProtobufHttpMessageConverter can only write protobuf Message objects (see ProtobufHttpMessageConverter supports method).

Spring Boot should provide a mechanism to ensure that the proper error is delivered to the client. A workaround for users is to create something like this:

package demo;

option java_package = "demo";
option java_outer_classname = "Data";

message MapFieldEntry {
    required string key = 1;
    required string value = 2;
}

message Error {
    repeated MapFieldEntry errors = 1;
}

Generate the respective Java classes. Then create a controller:

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package demo;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import demo.Data.Error.Builder;
import demo.Data.MapFieldEntry;

/**
 * @author Rob Winch
 */
@Controller
public class ErrorController {
    private final ErrorAttributes errorAttributes;

    @Autowired
    public ErrorController(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }

    @RequestMapping(value = "/error", produces = "application/x-protobuf")
    @ResponseBody
    public ResponseEntity<Data.Error> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request));

        Builder errorsBuilder = Data.Error.newBuilder();
        for(Map.Entry<String, Object> error : body.entrySet()) {
            demo.Data.MapFieldEntry.Builder entryBuilder = MapFieldEntry
                    .newBuilder()
                    .setKey(error.getKey())
                    .setValue(String.valueOf(error.getValue()));
            errorsBuilder.addErrors(entryBuilder.build());
        }
        Data.Error errors = errorsBuilder.build();
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Data.Error>(errors, status);
    }

    private boolean getTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        if (parameter == null) {
            return false;
        }
        return !"false".equals(parameter.toLowerCase());
    }

    private Map<String, Object> getErrorAttributes(HttpServletRequest request,
            boolean includeStackTrace) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes,
                includeStackTrace);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request
                .getAttribute("javax.servlet.error.status_code");
        if (statusCode != null) {
            try {
                return HttpStatus.valueOf(statusCode);
            }
            catch (Exception ex) {
            }
        }
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }
}

Related Links:

@philwebb philwebb added this to the 1.3.0 milestone Apr 15, 2015
@philwebb philwebb modified the milestones: 1.3.0.M3, 1.3.0.RC1 Aug 5, 2015
@dsyer dsyer modified the milestones: 1.3.0.M5, Backlog Aug 26, 2015
@philwebb philwebb removed this from the Backlog milestone Jan 7, 2016
@mwillema
Copy link

mwillema commented Aug 2, 2016

With the release of Protobuf 3 this should be easier since it supports map data types: https://developers.google.com/protocol-buffers/docs/proto3#maps

No need for a custom Protobuf message anymore (at least for this version)

@bclozel
Copy link
Member

bclozel commented Aug 2, 2016

Protobuf 3 is supported by Spring Framework as of 5.0 - and we've got a similar issue there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: external-project For an external project and not something we can fix
Projects
None yet
Development

No branches or pull requests

5 participants