require HTTP/1.1 for chunked responses

pull/4187/head
Thibault Duplessis 2018-04-03 06:27:40 +02:00
parent 1ae01f9368
commit 92ef2b9ae9
8 changed files with 84 additions and 61 deletions

View File

@ -282,9 +282,11 @@ object Api extends LilaController {
} map toApiResult
}
def gameStream = Action(parse.tolerantText) { req =>
val userIds = req.body.split(',').take(300).toSet map lila.user.User.normalize
Ok.chunked(Env.game.stream.startedByUserIds(userIds))
def gameStream = Action.async(parse.tolerantText) { req =>
RequireHttp11(req) {
val userIds = req.body.split(',').take(300).toSet map lila.user.User.normalize
Ok.chunked(Env.game.stream.startedByUserIds(userIds)).fuccess
}
}
def activity(name: String) = ApiRequest { implicit ctx =>

View File

@ -49,14 +49,16 @@ object Export extends LilaController {
def png(id: String) = Open { implicit ctx =>
OnlyHumansAndFacebookOrTwitter {
PngRateLimitGlobal("-", msg = s"${HTTPRequest.lastRemoteAddress(ctx.req).value} ${~HTTPRequest.userAgent(ctx.req)}") {
lila.mon.export.png.game()
OptionFuResult(GameRepo game id) { game =>
env.pngExport fromGame game map { stream =>
Ok.chunked(stream).withHeaders(
CONTENT_TYPE -> "image/png",
CACHE_CONTROL -> "max-age=7200"
)
RequireHttp11 {
PngRateLimitGlobal("-", msg = s"${HTTPRequest.lastRemoteAddress(ctx.req).value} ${~HTTPRequest.userAgent(ctx.req)}") {
lila.mon.export.png.game()
OptionFuResult(GameRepo game id) { game =>
env.pngExport fromGame game map { stream =>
Ok.chunked(stream).withHeaders(
CONTENT_TYPE -> "image/png",
CACHE_CONTROL -> "max-age=7200"
)
}
}
}
}
@ -65,21 +67,23 @@ object Export extends LilaController {
def puzzlePng(id: Int) = Open { implicit ctx =>
OnlyHumansAndFacebookOrTwitter {
PngRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) {
lila.mon.export.png.puzzle()
OptionFuResult(Env.puzzle.api.puzzle find id) { puzzle =>
env.pngExport(
fen = chess.format.FEN(puzzle.fenAfterInitialMove | puzzle.fen),
lastMove = puzzle.initialMove.uci.some,
check = none,
orientation = puzzle.color.some,
logHint = s"puzzle $id"
) map { stream =>
Ok.chunked(stream).withHeaders(
CONTENT_TYPE -> "image/png",
CACHE_CONTROL -> "max-age=7200"
)
}
RequireHttp11 {
PngRateLimitGlobal("-", msg = HTTPRequest.lastRemoteAddress(ctx.req).value) {
lila.mon.export.png.puzzle()
OptionFuResult(Env.puzzle.api.puzzle find id) { puzzle =>
env.pngExport(
fen = chess.format.FEN(puzzle.fenAfterInitialMove | puzzle.fen),
lastMove = puzzle.initialMove.uci.some,
check = none,
orientation = puzzle.color.some,
logHint = s"puzzle $id"
) map { stream =>
Ok.chunked(stream).withHeaders(
CONTENT_TYPE -> "image/png",
CACHE_CONTROL -> "max-age=7200"
)
}
}
}
}
}

View File

@ -3,6 +3,7 @@ package controllers
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import scala.concurrent.duration._
import play.api.mvc.RequestHeader
import lila.app._
import lila.game.{ GameRepo, Game => GameModel }
@ -36,7 +37,7 @@ object Game extends LilaController {
err => Env.security.forms.anyCaptcha map { captcha =>
BadRequest(html.game.export(err, captcha))
},
_ => fuccess(streamGamesPgn(me, since = none, until = none))
_ => streamGamesPgn(req, me, since = none, until = none)
)
}
@ -44,7 +45,7 @@ object Game extends LilaController {
val since = getLong("since", req) map { ts => new DateTime(ts) }
val until = getLong("until", req) map { ts => new DateTime(ts) }
val max = getInt("max", req)
fuccess(streamGamesPgn(me, since, until, max))
streamGamesPgn(req, me, since, until, max)
}
private val ExportRateLimitPerUser = new lila.memo.RateLimit[lila.user.User.ID](
@ -54,13 +55,15 @@ object Game extends LilaController {
key = "game_export.user"
)
private def streamGamesPgn(user: lila.user.User, since: Option[DateTime], until: Option[DateTime], max: Option[Int] = None) =
ExportRateLimitPerUser(user.id, cost = 1) {
val date = (DateTimeFormat forPattern "yyyy-MM-dd") print new DateTime
Ok.chunked(Env.api.pgnDump.exportUserGames(user.id, since, until, max | Int.MaxValue)).withHeaders(
CONTENT_TYPE -> pgnContentType,
CONTENT_DISPOSITION -> ("attachment; filename=" + s"lichess_${user.username}_$date.pgn")
)
private def streamGamesPgn(req: RequestHeader, user: lila.user.User, since: Option[DateTime], until: Option[DateTime], max: Option[Int] = None) =
RequireHttp11(req) {
ExportRateLimitPerUser(user.id, cost = 1) {
val date = (DateTimeFormat forPattern "yyyy-MM-dd") print new DateTime
Ok.chunked(Env.api.pgnDump.exportUserGames(user.id, since, until, max | Int.MaxValue)).withHeaders(
CONTENT_TYPE -> pgnContentType,
CONTENT_DISPOSITION -> ("attachment; filename=" + s"lichess_${user.username}_$date.pgn")
)
} fuccess
}
private[controllers] def preloadUsers(game: GameModel): Funit =

View File

@ -54,7 +54,9 @@ object Irwin extends LilaController {
def eventStream = Open { implicit ctx =>
ModExternalBot {
Ok.chunked(Env.irwin.stream.enumerator).fuccess
RequireHttp11 {
Ok.chunked(Env.irwin.stream.enumerator).fuccess
}
}
}

View File

@ -410,6 +410,12 @@ private[controllers] trait LilaController
else if (HTTPRequest isBot ctx.req) fuccess(NotFound)
else result
protected def RequireHttp11(result: => Fu[Result])(implicit ctx: lila.api.Context): Fu[Result] =
RequireHttp11(ctx.req)(result)
protected def RequireHttp11(req: RequestHeader)(result: => Fu[Result]): Fu[Result] =
if (HTTPRequest isHttp10 req) BadRequest("Requires HTTP 1.1").fuccess
else result
private val jsonGlobalErrorRenamer = {
import play.api.libs.json._
__.json update (

View File

@ -81,23 +81,25 @@ object Search extends LilaController {
def export = OpenBody { implicit ctx =>
NotForBots {
implicit def req = ctx.body
searchForm.bindFromRequest.fold(
failure => Env.game.cached.nbTotal map { nbGames =>
Ok(html.search.index(failure, none, nbGames))
},
data => data.nonEmptyQuery ?? { query =>
env.api.ids(query, 5000) map { ids =>
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
val date = (DateTimeFormat forPattern "yyyy-MM-dd") print DateTime.now
Ok.chunked(Env.api.pgnDump exportGamesFromIds ids).withHeaders(
CONTENT_TYPE -> pgnContentType,
CONTENT_DISPOSITION -> ("attachment; filename=" + s"lichess_search_$date.pgn")
)
RequireHttp11 {
implicit def req = ctx.body
searchForm.bindFromRequest.fold(
failure => Env.game.cached.nbTotal map { nbGames =>
Ok(html.search.index(failure, none, nbGames))
},
data => data.nonEmptyQuery ?? { query =>
env.api.ids(query, 5000) map { ids =>
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
val date = (DateTimeFormat forPattern "yyyy-MM-dd") print DateTime.now
Ok.chunked(Env.api.pgnDump exportGamesFromIds ids).withHeaders(
CONTENT_TYPE -> pgnContentType,
CONTENT_DISPOSITION -> ("attachment; filename=" + s"lichess_search_$date.pgn")
)
}
}
}
)
)
}
}
}
}

View File

@ -70,15 +70,17 @@ object Tv extends LilaController {
}
}
def feed = Action.async {
import makeTimeout.short
import akka.pattern.ask
import lila.round.TvBroadcast
import play.api.libs.EventSource
Env.round.tvBroadcast ? TvBroadcast.GetEnumerator mapTo
manifest[TvBroadcast.EnumeratorType] map { enum =>
Ok.chunked(enum &> EventSource()).as("text/event-stream")
}
def feed = Action.async { req =>
RequireHttp11(req) {
import makeTimeout.short
import akka.pattern.ask
import lila.round.TvBroadcast
import play.api.libs.EventSource
Env.round.tvBroadcast ? TvBroadcast.GetEnumerator mapTo
manifest[TvBroadcast.EnumeratorType] map { enum =>
Ok.chunked(enum &> EventSource()).as("text/event-stream")
}
}
}
def embed = Action { req =>

View File

@ -71,4 +71,6 @@ object HTTPRequest {
def printClient(req: RequestHeader) = s"${lastRemoteAddress(req)} origin:${~origin(req)} referer:${~referer(req)} ua:${~userAgent(req)}"
def isOAuth(req: RequestHeader) = req.headers.toMap.contains(HeaderNames.AUTHORIZATION)
def isHttp10(req: RequestHeader) = req.version == "HTTP/1.0"
}