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

SPECs feature: Websocket / Upgrade support proposal #1093

Closed
boazsegev opened this Issue Jul 7, 2016 · 9 comments

Comments

Projects
None yet
4 participants
@boazsegev

boazsegev commented Jul 7, 2016

EDIT ...I know some of the comments refer to the text I am removing and I apologize for my rudeness... but I'm positive that condensing this proposal will help focusing the issue and making practical changes.

Some of the rational behind the proposal had moved to my blog post regarding this proposal.

The following is the edited proposal.


This is a proposal for a Rack specification extension - totally backwards compatible - for allowing "hijack-less" websocket upgrades usingenv['upgrade.websocket'] (of, for plain TCP/IP, using env['upgrade.tcp'])

You may have read my blog post about this or the reddit discussion.

This proposal simplifies Websocket applications by leaving all the network complexity were it is (with the application's web server), allowing application programmers to focus on their application logic.

Using the proposed specification, a pure Rack Websocket echo server could be written as simply as:

# this is a toy example.
class MyEcho
   def initialize(env = nil)
      # optional initialization
   end
   def on_message(data)
     write "Echo: #{data}"
   end
end

app = Proc.new do |env|
   if env['upgrade.websocket?'] && env['HTTP_UPGRADE'] =~ /websocket/i
      env['upgrade.websocket'] = MyEcho.new(env) # or simply `MyEcho`
      [ 0, {'X-Header': 'data'}, [] ]
   else
      [200, { 'Content-Length' => '12' }, ['He11o World!']]
   end
end

run app

There's a working Gist chatroom example.

What is the best way to promote this proposal, get it reviewed and push it into the Rack specification?

@boazsegev

This comment has been minimized.

Show comment
Hide comment
@boazsegev

boazsegev Jul 17, 2016

I know everyone was busy with the 2.0.1 release, but is anyone assigned to this?

boazsegev commented Jul 17, 2016

I know everyone was busy with the 2.0.1 release, but is anyone assigned to this?

@Thermatix

This comment has been minimized.

Show comment
Hide comment
@Thermatix

Thermatix Aug 4, 2016

This looks super interesting, I've thinking about trying something sockets related but everything about it seem over the top or over engineered, I like this becuase it's simple and super easy to understand, I like it when things just get out of the way and let you get on with it.

Question, since it looks like it supports the same basic three item array that standard rack uses, could you run grape or sinatra on it?, or are you intending to build a simple DSL that handles the weird/hard stuff? Not saying I couldn't live with this (built my own sub-domain rack middleware), just wondering.

Thermatix commented Aug 4, 2016

This looks super interesting, I've thinking about trying something sockets related but everything about it seem over the top or over engineered, I like this becuase it's simple and super easy to understand, I like it when things just get out of the way and let you get on with it.

Question, since it looks like it supports the same basic three item array that standard rack uses, could you run grape or sinatra on it?, or are you intending to build a simple DSL that handles the weird/hard stuff? Not saying I couldn't live with this (built my own sub-domain rack middleware), just wondering.

@boazsegev

This comment has been minimized.

Show comment
Hide comment
@boazsegev

boazsegev Aug 4, 2016

Hi Martin @Thermatix , thanks for the positive feedback.

The proposed websocket solution is backwards compatible and should work with existing Ruby Frameworks. You should be able to safely use Sinatra / Grape / Rails with this.

However, existing frameworks might not forward websocket upgrade requests to your controller. For example, Rails will forward the request to ActionCable (and in a better future it will use this solution to make ActionCable easier to manage).

When using this solution with a Rack based framework such as Sinatra or Grape (instead of pure Rack), I would recommend using middleware to intercept the upgrade request.

Still, this is all very experimental and I'm hoping someone from Rack's team will support this extension to Rack's features so that this easy to follow workflow will become the standard way of handling Websocket upgrades.

At the moment Iodine is the only server (I know of) that supports this approach.

But Iodine 0.2.0 is a toy server used for a proof of concept. It still has hidden issues. For example, just a few hours ago I fixed an issue where larger uploads (that are stored in temporary files instead of the system's memory) would either fail to upload or throw exceptions.

I hope Iodine will be production ready in the near future (I plan for it to be the core for plezi.io's updated release), but I believe it would be better if the proposed solution was implemented by major Ruby servers, so it's better tested and wildly available.

P.S.

No DSL is envisioned as part of the proposed solution or the Iodine Server. All the synthetic sugar is left for the Rack based framework implementation.

boazsegev commented Aug 4, 2016

Hi Martin @Thermatix , thanks for the positive feedback.

The proposed websocket solution is backwards compatible and should work with existing Ruby Frameworks. You should be able to safely use Sinatra / Grape / Rails with this.

However, existing frameworks might not forward websocket upgrade requests to your controller. For example, Rails will forward the request to ActionCable (and in a better future it will use this solution to make ActionCable easier to manage).

When using this solution with a Rack based framework such as Sinatra or Grape (instead of pure Rack), I would recommend using middleware to intercept the upgrade request.

Still, this is all very experimental and I'm hoping someone from Rack's team will support this extension to Rack's features so that this easy to follow workflow will become the standard way of handling Websocket upgrades.

At the moment Iodine is the only server (I know of) that supports this approach.

But Iodine 0.2.0 is a toy server used for a proof of concept. It still has hidden issues. For example, just a few hours ago I fixed an issue where larger uploads (that are stored in temporary files instead of the system's memory) would either fail to upload or throw exceptions.

I hope Iodine will be production ready in the near future (I plan for it to be the core for plezi.io's updated release), but I believe it would be better if the proposed solution was implemented by major Ruby servers, so it's better tested and wildly available.

P.S.

No DSL is envisioned as part of the proposed solution or the Iodine Server. All the synthetic sugar is left for the Rack based framework implementation.

@Thermatix

This comment has been minimized.

Show comment
Hide comment
@Thermatix

Thermatix Aug 4, 2016

@boazsegev sorry, should have been specific, I meant a DSL separately, to make using iodine's sockets easier.

I kinda guessed I'd have to hack a middleware to forward socket requests but could I use sintra/grape to format them ? I suppose I could make a middleware that communicates in binary , I remember reading an interesting article on binary field formatting for Java Script, even came with a simple library to make it easier.

Do do the same on the server and then use it to format data and then stream said data to the front end in pure binary and then decode it on the front end.

Interesting.

P.s. why binary? why not, sides it's kinda what I think when I read streaming, small binary packets flowing to and from the server, since it's not that hard and would allow you to pack the most amount of data, could even use simple RLE as a simple compression scheme on the binary. Double decoding/encoding, first encode the data into binary using bit masks, then encode for RLE, send data then do the opposite on the other end.

P.P.s, i know, off topic, kinda can't help myself when it comes to things that interest me.

Thermatix commented Aug 4, 2016

@boazsegev sorry, should have been specific, I meant a DSL separately, to make using iodine's sockets easier.

I kinda guessed I'd have to hack a middleware to forward socket requests but could I use sintra/grape to format them ? I suppose I could make a middleware that communicates in binary , I remember reading an interesting article on binary field formatting for Java Script, even came with a simple library to make it easier.

Do do the same on the server and then use it to format data and then stream said data to the front end in pure binary and then decode it on the front end.

Interesting.

P.s. why binary? why not, sides it's kinda what I think when I read streaming, small binary packets flowing to and from the server, since it's not that hard and would allow you to pack the most amount of data, could even use simple RLE as a simple compression scheme on the binary. Double decoding/encoding, first encode the data into binary using bit masks, then encode for RLE, send data then do the opposite on the other end.

P.P.s, i know, off topic, kinda can't help myself when it comes to things that interest me.

@boazsegev

This comment has been minimized.

Show comment
Hide comment
@boazsegev

boazsegev Aug 4, 2016

Actually it might be me that should apologize about not making my original post a bit clearer. I wrote a better (and longer) post about it here, and began a reddit discussion.

The main purpose of this proposed solution was to support the Websocket protocol as a native implementation performed by the server (instead of the client), thereby optimizing resource use and minimizing the application developer's need to know anything about networking.

For this purpose I proposed the env['rack.websocket'] = WebsocketObject approach (for now, until this is approved: iodine.websocket). This would allow server side implementation of the Websocket protocol using a Websocket Callback Object.

However, supporting Websockets is actually a step further then supporting raw binary sockets (the iodine.upgrade which should probably be renamed to iodine.raw or iodine.tcp).

Since implementing callbacks for the raw TCP/IP socket was practically coded anyway, I though that people who want to use TCP/IP sockets should be allowed to implement their on solutions (assuming the client is prepared to upgrade an HTTP request to a raw TCP/IP socket connection instead of the standard Websocket protocol that browsers use).

This is also why there isn't any DSL to make raw TCP/IP socket implementations any easier. I assume that if a developer preferred raw TCP/IP over the Websocket protocol, they probably have something very specific in mind.

As for RLE and other transport layer details, these would require coding a client as well. There's an advantage to using Websockets for the transport layer protocol, since this means we can utilize existing clients such as browsers and existing libraries.

boazsegev commented Aug 4, 2016

Actually it might be me that should apologize about not making my original post a bit clearer. I wrote a better (and longer) post about it here, and began a reddit discussion.

The main purpose of this proposed solution was to support the Websocket protocol as a native implementation performed by the server (instead of the client), thereby optimizing resource use and minimizing the application developer's need to know anything about networking.

For this purpose I proposed the env['rack.websocket'] = WebsocketObject approach (for now, until this is approved: iodine.websocket). This would allow server side implementation of the Websocket protocol using a Websocket Callback Object.

However, supporting Websockets is actually a step further then supporting raw binary sockets (the iodine.upgrade which should probably be renamed to iodine.raw or iodine.tcp).

Since implementing callbacks for the raw TCP/IP socket was practically coded anyway, I though that people who want to use TCP/IP sockets should be allowed to implement their on solutions (assuming the client is prepared to upgrade an HTTP request to a raw TCP/IP socket connection instead of the standard Websocket protocol that browsers use).

This is also why there isn't any DSL to make raw TCP/IP socket implementations any easier. I assume that if a developer preferred raw TCP/IP over the Websocket protocol, they probably have something very specific in mind.

As for RLE and other transport layer details, these would require coding a client as well. There's an advantage to using Websockets for the transport layer protocol, since this means we can utilize existing clients such as browsers and existing libraries.

@boazsegev

This comment has been minimized.

Show comment
Hide comment
@boazsegev

boazsegev Aug 5, 2016

I opened a proposed specification discussion page on Iodine's repo at boazsegev/iodine#6.

This has everything I learned so far from implementing this proposed solution and getting all the functionality I needed for a scalable application using pub/sub (with Redis)... or, at least it has all the important relating to the implementation API.

boazsegev commented Aug 5, 2016

I opened a proposed specification discussion page on Iodine's repo at boazsegev/iodine#6.

This has everything I learned so far from implementing this proposed solution and getting all the functionality I needed for a scalable application using pub/sub (with Redis)... or, at least it has all the important relating to the implementation API.

@evanphx

This comment has been minimized.

Show comment
Hide comment
@evanphx

evanphx Aug 19, 2016

Contributor

I have no problem with adding support for this, I think it would be a good thing. Obviously it needs to remain optional though within rack.

Contributor

evanphx commented Aug 19, 2016

I have no problem with adding support for this, I think it would be a good thing. Obviously it needs to remain optional though within rack.

@macournoyer

This comment has been minimized.

Show comment
Hide comment
@macournoyer

macournoyer Aug 19, 2016

Member

I like the API a lot! This will make a lot more sense to implement in Thin then the Hijacking API.

Currently the -1 status code is used denoting an async response. Perhaps use this instead of 0?

Other then that, 👍

Member

macournoyer commented Aug 19, 2016

I like the API a lot! This will make a lot more sense to implement in Thin then the Hijacking API.

Currently the -1 status code is used denoting an async response. Perhaps use this instead of 0?

Other then that, 👍

@boazsegev

This comment has been minimized.

Show comment
Hide comment
@boazsegev

boazsegev Aug 28, 2016

A Pull Request was opened with the updated proposal (dropping each-defer methods and adding has_pending? and on_ready).

boazsegev commented Aug 28, 2016

A Pull Request was opened with the updated proposal (dropping each-defer methods and adding has_pending? and on_ready).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment