raml-org / raml-spec

RAML Specification
http://raml.org
3.87k stars 858 forks source link

Allow Descriptions/Definitions for Arbitrary HTTP Methods #192

Open ejmr opened 8 years ago

ejmr commented 8 years ago

First please see my comments on #95. The HTTP/1.1 specification allows servers to implement arbitrary methods but these are not possible to describe in RAML 0.8. Here is a personal use-case for defining new HTTP methods. I wanted to use RAML for that project, but as it is the lack of support for arbitrary HTTP methods made it impossible to use.

Ignoring my own use-case though, in general I think it is a mistake for RAML to restrict support for HTTP methods in any way, e.g. supporting only a fixed list of methods. This puts RAML in contrast with section 5.1.1 of the HTTP/1.1 standard, making it impossible to describe certain APIs with RAML which are perfectly acceptable and valid under RFC 2616.

I have not yet tried modifying one of the various RAML tools to patch in support for arbitrary HTTP methods though, so I don't have a technical solution to present at the moment. My general idea is that wherever RAML allows things like /get or /post, it should accept /foo where foo could be any name that could be a valid HTTP method name.

sichvoge commented 8 years ago

It's definitely a valid point and I see that it is part of the HTTP/1.1 specification. I also asked a little bit around and it seems WEBDAV also implemented custom methods. I believe that we should follow the HTTP spec, but not sure how easy it is to support it in general. How are custom methods defined? Are they restricted to have a request/response body, etc. like every other method?

ejmr commented 8 years ago

How are custom methods defined? Are they restricted to have a request/response body, etc. like every other method?

Custom methods are defined by the server, which is to say the server has the "authority" to tells clients what is a method and what isn't, which is done using the Allow response header; it lists all of the methods valid for any given URI. Generally (although not required) OPTIONS will return the Allow header, which will be something like...

Allow: OPTIONS, HEAD, GET, PUT

...which clients can use to recognize what methods are allowed for that entity. When a PUT request creates a new entity the server should (in the RFC 2119 sense) return an Allow header listing all acceptable methods for that entity; the standard doesn't make this mandatory though. Allow is the only way a server can tell clients about custom methods. And the only situation where the server must (again RFC 2119) return the Allow header is when it sends a 405 Method Not Allowed response, which somewhat ironically makes that the best way for a client to "discover" the existence of a method. I quote "discover" specifically to highlight that the HTTP/1.1 standard, with regard to custom methods, is designed around the idea of both the server and client being ignorant of what methods are available. So those custom methods are "defined" essentially to the extent that they are made discoverable by the server supporting them.

Are they restricted to have a request/response body, etc. like every other method?

Custom methods are required to have the Host header, which all HTTP/1.1 methods must have, but that's it. They are not required to have a response body, in which case the server would return something like 204 No Content for a successful request involving a custom method that doesn't produce a response body. But their responses can have bodies, in which case they would have the usual headers describing the content type, etc., to give the client as much information as possible about what the server is sending back.

sichvoge commented 8 years ago

Thank you @ejmr

ejmr commented 8 years ago

No problem. Sometime this week I hope to write an example of what I think a custom method definition in RAML would look like using my own use-case as an example, and then see what challenges I run into hacking support for that into one or two tools. Can't guarantee I'll get it done, but that's high on my list of "stuff to do when I have free time to kill", heh.

sichvoge commented 8 years ago

that would be cool - post that here for everyone to decide if that goes into the spec officially. @usarid for you visibility.

ejmr commented 8 years ago

Here's a brainstorming example I came up with.

#%RAML 1.0
title: Example of a custom HTTP method
mediaType: application/json

types:
  Player:
    type: object
    properties:
      id:       number
      name:     string
      position: number

/players/{id}:

  # The "allow" property provides a definition for the `Allow`
  # header which the server might return if given the `OPTIONS`
  # method, and which it *MUST* return when given any method that
  # results in a 405 Method Not Found response.
  #
  # This list also describes in RAML which methods are valid for this
  # resource.  In this example we define a non-standard `RESET`
  # method.
  allow:
    - options
    - head
    - get
    - put
    - delete
    - reset

  get:
    responses:
      200:
        application/json:
          type: Player

  options:
    responses:
      200:
        headers:
          Allow: OPTIONS, HEAD, GET, PUT, DELETE, RESET

  # The `POST` method is not listed as an allowed method in the list
  # above.  Therefore, in accordance with the HTTP/1.1 standard, the
  # server *MUST* return a 405 Method Not Found which *MUST* include
  # the `Allow` header, just as the header was defined for `OPTIONS`.
  # A smart RAML processor could take the `allow` list and generate
  # the the `Allow` response header for all 405 responses
  # automatically.
  post:
    responses:
      405:
        # Automatically generated based on `allow`...
        #
        # headers:
        #   Allow: OPTIONS, HEAD, GET, PUT, DELETE, RESET

  # If a `PUT` request creates a new resource then the server must
  # return a 201 Created response.  That response can (but is not
  # required to) include an `Allow` header describing the methods
  # available on the new resource.  Again, a RAML tool which sees the
  # `allow` property for this URI could implicitly define that header
  # for a 201 Created response.
  put:
    responses:
      202:
        # Implicitly includes the `Allow` header.
      204:
        # It would not be incorrect to implicitly define the header
        # for this response either.

  # Other standard methods omitted for brevity...

  # Our non-standard method.  HTTP/1.1 gives custom methods the
  # freedom to accept and return whatever they want.  The `Host`
  # header is the only header which all methods *MUST* accept.  RAML
  # already describes this header implicitly in the form of base URIs
  # and resource definitions themselves; and an HTTP server which
  # doesn't handle `Host` is flat-out broken and unusable anyway, so
  # we have no need to explicitly define that request header here.
  #
  # Responses for custom methods are not required to have a body, and
  # this example does not, which is why it returns 204 No Content.
  reset:
    responses:
      204:
maqdev commented 8 years ago

+1 to this, we're also internally using custom methods.

freddrake commented 8 years ago

It seems relevant to point out that HTTP/1.1 methods are case-sensitive and defined using the "token" production; see details in the EBNF:

https://tools.ietf.org/html/rfc7230#appendix-B

   tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
    "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
   token = 1*tchar

While I've not seen anyone using or proposing a method that doesn't use SHOUTY-CAPS, and there's precedent for using lower-cased versions of method names when referring to methods (as in HTML <form method="...">), not using method names as they really exist in HTTP seems a bad idea.

My inner pedant says we should end up using GET, PATCH, POST, etc., in RAML documents, though my hatred for the shift key makes me like the lower-cased names better, so I'm a little torn.