Implications of stateless servlets & how/whether to avoid them

102 views
Skip to first unread message

Brian Adkins

unread,
Nov 30, 2018, 2:17:11 PM11/30/18
to Racket Users
A while ago, I read Jay's response about how to use the Racket web server w/o continuations here:


At the time, I didn't dig very deeply into it and just assumed avoiding web-server/servlet would be sufficient, but I just read through the documentation on stateless servlets here:


In particular, section 3.2, where it states things like:

"All uses of letrec are removed and replaced with equivalent uses of let and imperative features."

"The program is defunctionalized with a serializable data-structure for each lambda"

"First, this process drastically changes the structure of your program. It will create an immense number of lambdas and structures your program did not normally contain. The performance implication of this has not been studied with Racket."

It seems like there is quite a bit of stuff going on to support continuations with stateless servlets. Since I'm not planning on using continuations at all, I'm not sure I want the changes to my code described in section 3.2.

I'm coming from an entirely stateless architecture w/ Ruby/Rails, and I was planning on using a similar style w/ Racket, so I'm just trying to get a feel for how low in the stack I need to be to avoid the extra functionality that I don't want/need. Eventually, I'm planning on resuming work on a web app framework in Racket that steals my favorite things from Rails & other frameworks, and leaves out the cruft. For that, I expect I'll need to base my code on lower levels, but for my current app, I don't have time to create too much infrastructure, so I'd like to leverage basic things from built-in Racket functionality without going "too far", and I'm not even able to articulate well where the "too far" line is.

Thanks,
Brian

Jay McCarthy

unread,
Nov 30, 2018, 3:20:39 PM11/30/18
to Brian Adkins, racket...@googlegroups.com
There's nothing wrong with ignoring the continuation support in the
Web server, either the native ones or stateless ones. If you do, I
recommend using something like `create-none-manager` [1] as the
`#:manager` argument to `serve/servlet` so that you don't accidentally
start using them. The "too far" line is that you can't use
`send/suspend`. In the web-server/servlet/web [2] module, you just
want to use `send/back` and `with-errors-to-browser`, and no other
functions.

Jay

1. https://docs.racket-lang.org/web-server/servlet.html?q=none-manager#%28def._%28%28lib._web-server%2Fmanagers%2Fnone..rkt%29._create-none-manager%29%29
2. https://docs.racket-lang.org/web-server/servlet.html?q=send%2Fsuspend#%28part._web%29
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
-=[ Jay McCarthy http://jeapostrophe.github.io ]=-
-=[ Associate Professor PLT @ CS @ UMass Lowell ]=-
-=[ Moses 1:33: And worlds without number have I created; ]=-

Brian Adkins

unread,
Nov 30, 2018, 4:30:42 PM11/30/18
to Racket Users
I could be misreading the information in "3.2 Usage Considerations", but it seemed like the modifications to my program were automatic, but maybe that only happens when using #lang web-server or #lang web-server/base ?

Regardless, I'm wondering if maybe I should just use (serve) instead of (serve/servlet) since I'll likely be working at that level later anyway. In that case, it looks like dispatch-lift:make is the main thing I need to get things rolling. As a simple example:

#lang racket
(require web-server/web-server)
(require web-server/http/response-structs)
(require (prefix-in log: web-server/dispatchers/dispatch-log))
(require (prefix-in lift: web-server/dispatchers/dispatch-lift))
(require (prefix-in seq: web-server/dispatchers/dispatch-sequencer))
(require (prefix-in stat: web-server/dispatchers/dispatch-stat))

(define (controller request)
  (response
   200
   #"OK"
   (current-seconds)
   TEXT/HTML-MIME-TYPE
   empty
   (λ (op) (write-bytes #"<html><body>Hello, World!</body></html>" op))))

(serve
 #:dispatch (seq:make (log:make #:format log:extended-format
                                #:log-path "development.log") ; log request
                      (stat:make) ; print memory usage
                      (lift:make controller))
 #:port 8080)

To be clear, it's not just continuations that I want to avoid, I'd also like to avoid the changes that are described in section 3.2 above.

Jay McCarthy

unread,
Nov 30, 2018, 8:25:40 PM11/30/18
to Brian Adkins, racket...@googlegroups.com
Hi Brian,

I think you are misunderstanding what that section is about. It is
just describing how the system is implemented. There's basically
nothing in there that you need to know as user other than "It may take
a while to compile." For instance, you don't worry about the fact that
all tree-like functions in your normal Racket code are eventually
turned into linear sequences of assembly. Those changes to your code
discussed in 3.2 are things that happen in the compiler, you don't
need to do anything, just like you don't need to think about register
allocation when you write normal programs, but it happens behind the
scenes.

Nothing described in section 3 happens to your code unless you write
in `#lang web-server` or `#lang web-server/base`. You can use or not
use continuations and use or not use this library... they are totally
orthogonal. There is basically no program that you can write in one
that you can't write in the other, as long as you call the appropriate
version of `send/suspend`.

The whole point of this library is to write code as-if it were
stateful, but the compiler automatically makes it stateless. If you
are comfortable programming directly with inverted control, then go
right ahead and implement the stateless stuff yourself. Both ways are
going to be equally efficient, although the `#lang web-server` library
will be guaranteed to do it correctly and make it easy to do stuff
like encrypt and sign the state you store on the clients.

As far as using `serve/servlet` or not, the implementation of it is
really simple [1] in case you want to adapt it. I don't recommend
using the lift dispatcher directly. You probably want to use
`dispatch/servlet`. Remember, in Racket, a servlet is just a function
from request to response, with some resource control. It doesn't
impose any programming style or other costs on you. I get the
impression from your comments that you are really nervous about some
sort of costs imposed by using Racket libraries and think you will get
some benefit by being "low-level". This is probably misguided and just
based on some misunderstandings.

Jay

1. https://github.com/racket/web-server/blob/master/web-server-lib/web-server/servlet-env.rkt#L156

Brian Adkins

unread,
Nov 30, 2018, 9:43:43 PM11/30/18
to Racket Users
Jay:

Thanks for taking the time to educate me a bit. I expect you are right regarding some/most of my concerns being due to misunderstandings on my part. Despite playing with Racket for a few years, it wasn't until this particular project (with a deadline) that I really began to dig in more deeply. I admit I don't yet fully understand continuations in general, let alone in the context of the web server, but I've had great success with the stateless approach that Rails uses.

I've been bitten by "leaky abstractions" in the past, and Rails uses a lot of "magic" that mostly works well, but sometimes does not, so I'm trying to better understand the foundation I'll be building upon with Racket along with the pros/cons of various approaches. Having said that, I wouldn't at all be surprised if I end up using some of the stateless functionality you've developed in the future. And I have some apps that may benefit from stateful continuations, so I'll be experimenting with that after this initial project.

Using the lift dispatcher directly didn't feel quite right, so I'll look into dispatch/servlet. Will dispatch/servlet spin up a thread for each request as serve/servlet does? If so, that will save me from handling the request threading myself. I don't think I need a stuffer or manager though, so I'm not sure dispatch/sev

I don't know if the Racket web server (or related libraries) currently provide a way to stream data in the response, but that is something I'll definitely need relatively soon (primarily for streaming large CSV/JSON files). If it doesn't exist, I don't mind writing it, but I also don't want to begin with an approach now that might make adding that capability more difficult later. From my brief research, given the output field of the response struct is a lambda, I think I can stream using that (i.e. return a lambda in the response immediately that begins writing the data as it retrieves it) - hopefully the infrastructure doesn't buffer the entire output. Other than something like that, I'm happy to work mostly at the level of functions from requests to responses.

I will need full control over the URL, and I thought I read somewhere that some servlets needed to use the URL for state in some cases. That wouldn't work for me.

I'm confused about your statement, "...make it easy to do stuff like encrypt and sign the state you store on the clients". I would think encrypting and signing something would be simple function calls unrelated to whether I'm using servlets or not. Is this not the case? My client-side state needs are minimal - typically a session ID is sufficient. I wasn't aware of functionality built-in to the web server for this, so I just assumed I would have a secret key on the server that I'd use to encrypt state. I haven't gotten that far yet.

I had to look up send/suspend, etc., and I don't think I'd be using anything like that.

Thanks,
Brian

Jay McCarthy

unread,
Dec 3, 2018, 10:49:40 AM12/3/18
to Brian Adkins, racket...@googlegroups.com
On Fri, Nov 30, 2018 at 9:43 PM Brian Adkins <lojic...@gmail.com> wrote:
> Using the lift dispatcher directly didn't feel quite right, so I'll look into dispatch/servlet. Will dispatch/servlet spin up a thread for each request as serve/servlet does? If so, that will save me from handling the request threading myself. I don't think I need a stuffer or manager though, so I'm not sure dispatch/sev

The one thread per request rule happens at a lower level in the heart
of the Web server.

> I don't know if the Racket web server (or related libraries) currently provide a way to stream data in the response, but that is something I'll definitely need relatively soon (primarily for streaming large CSV/JSON files). If it doesn't exist, I don't mind writing it, but I also don't want to begin with an approach now that might make adding that capability more difficult later. From my brief research, given the output field of the response struct is a lambda, I think I can stream using that (i.e. return a lambda in the response immediately that begins writing the data as it retrieves it) - hopefully the infrastructure doesn't buffer the entire output. Other than something like that, I'm happy to work mostly at the level of functions from requests to responses.

The reason why responses have the lambda rather than a byte string is
specifically for streaming like you want. Make sure you specify the
correct response size in the headers.

> I will need full control over the URL, and I thought I read somewhere that some servlets needed to use the URL for state in some cases. That wouldn't work for me.

`send/suspend` may use the URL(*) for state, but that is it. There's
no other part of the Web server that forces your URL in any way.

> I'm confused about your statement, "...make it easy to do stuff like encrypt and sign the state you store on the clients". I would think encrypting and signing something would be simple function calls unrelated to whether I'm using servlets or not. Is this not the case? My client-side state needs are minimal - typically a session ID is sufficient. I wasn't aware of functionality built-in to the web server for this, so I just assumed I would have a secret key on the server that I'd use to encrypt state. I haven't gotten that far yet.

When `send/suspend` manipulates the URL (*or a POST parameter) to
store the state of your program, then you are storing information on
the client. That information is therefore susceptible to attack or
manipulation. The Web server gives you a simple way to either sign or
encrypt that information transparently, so that it is always done and
you never forget.

Jay

Brian Adkins

unread,
Feb 20, 2020, 11:16:55 AM2/20/20
to Racket Users
On Monday, December 3, 2018 at 10:49:40 AM UTC-5, Jay McCarthy wrote:
> I don't know if the Racket web server (or related libraries) currently provide a way to stream data in the response, but that is something I'll definitely need relatively soon (primarily for streaming large CSV/JSON files). If it doesn't exist, I don't mind writing it, but I also don't want to begin with an approach now that might make adding that capability more difficult later. From my brief research, given the output field of the response struct is a lambda, I think I can stream using that (i.e. return a lambda in the response immediately that begins writing the data as it retrieves it) - hopefully the infrastructure doesn't buffer the entire output. Other than something like that, I'm happy to work mostly at the level of functions from requests to responses.

The reason why responses have the lambda rather than a byte string is
specifically for streaming like you want. Make sure you specify the
correct response size in the headers. 

Jay, are you sure it's necessary to specify the correct response size in the headers? This would be extremely inconvenient in a streaming response scenario. I looked at some similar code in a Rails project, and it appears I'm only setting Last-Modified, Content-Disposition and Content-Type headers.

Yes, I know this is an old thread, but I'm just now having to stream some CSV files from the web app :) 

Jay McCarthy

unread,
Feb 20, 2020, 12:40:06 PM2/20/20
to Brian Adkins, Racket Users
I assume it is not necessary to be totally accurate, but it is good to when you can, because of the Web principle of accepting broad input and producing specific output. I don't know of any existing program (like a proxy or something) that would fail without an accurate length, but it wouldn't surprise me if there was one.

Jay

--
Jay McCarthy
Associate Professor @ CS @ UMass Lowell
http://jeapostrophe.github.io
Vincit qui se vincit.


--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.

Jon Zeppieri

unread,
Feb 20, 2020, 12:45:00 PM2/20/20
to Jay McCarthy, Brian Adkins, Racket Users
When you stream the response, it doesn't use a chunked transfer encoding? -Jon
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CAJYbDa%3Dr%2BqARXUCh90Fw1b5c7XqGCyenZcO--t02vK6Sae7wLA%40mail.gmail.com.

Bogdan Popa

unread,
Feb 20, 2020, 1:13:37 PM2/20/20
to Jon Zeppieri, Jay McCarthy, Brian Adkins, Racket Users

Jon Zeppieri writes:

> When you stream the response, it doesn't use a chunked transfer encoding? -Jon

The web server chunks all responses on HTTP/1.1 connections[1].

I can confirm that the web server works great[2] for streaming uses cases
like long polling!

[1]: https://github.com/racket/web-server/blob/564120c0f5e4bb959d4592a37037146d8411948e/web-server-lib/web-server/http/response.rkt#L56
[2]: https://github.com/Bogdanp/nemea/blob/5c8a58121ed9f264277d7eb56187fbeaad506138/nemea/http/reporting.rkt#L18-L35

Jon Zeppieri

unread,
Feb 20, 2020, 2:03:26 PM2/20/20
to Bogdan Popa, Jay McCarthy, Brian Adkins, Racket Users
Okay, in that case, you really shouldn't add a content-length header. -Jon
Reply all
Reply to author
Forward
0 new messages