1
1
package spark .jobserver
2
2
3
+ import java .util .NoSuchElementException
3
4
import java .util .concurrent .TimeUnit
5
+ import javax .net .ssl .SSLContext
4
6
5
- import akka .actor .{ ActorSystem , ActorRef }
7
+ import akka .actor .{ActorRef , ActorSystem }
6
8
import akka .pattern .ask
7
9
import akka .util .Timeout
8
- import com .typesafe .config .{ Config , ConfigFactory , ConfigException , ConfigRenderOptions }
9
- import java .util .NoSuchElementException
10
- import javax .net .ssl .SSLContext
11
- import ooyala .common .akka .web .{ WebService , CommonRoutes }
10
+ import com .typesafe .config .{Config , ConfigException , ConfigFactory , ConfigRenderOptions }
11
+ import ooyala .common .akka .web .JsonUtils .AnyJsonFormat
12
+ import ooyala .common .akka .web .{CommonRoutes , WebService }
13
+ import org .apache .shiro .SecurityUtils
14
+ import org .apache .shiro .config .IniSecurityManagerFactory
12
15
import org .joda .time .DateTime
13
16
import org .slf4j .LoggerFactory
14
- import spark .jobserver .util .SparkJobUtils
15
- import spark .jobserver .util .SSLContextFactory
16
- import spark .jobserver .routes .DataRoutes
17
- import scala .concurrent .{Await , ExecutionContext , Future }
18
- import scala .util .Try
19
- import spark .jobserver .io .JobInfo
20
17
import spark .jobserver .auth ._
21
- import spray .http .HttpResponse
22
- import spray .http .MediaTypes
23
- import spray .http .StatusCodes
18
+ import spark .jobserver .io .JobInfo
19
+ import spark .jobserver .routes .DataRoutes
20
+ import spark .jobserver .util .{SSLContextFactory , SparkJobUtils }
21
+ import spray .http ._
24
22
import spray .httpx .SprayJsonSupport .sprayJsonMarshaller
23
+ import spray .io .ServerSSLEngineProvider
25
24
import spray .json .DefaultJsonProtocol ._
26
- import spray .routing .{ HttpService , Route , RequestContext }
27
25
import spray .routing .directives .AuthMagnet
28
- import spray .io .ServerSSLEngineProvider
29
- import org .apache .shiro .config .IniSecurityManagerFactory
30
- import org .apache .shiro .mgt .SecurityManager
31
- import org .apache .shiro .SecurityUtils
26
+ import spray .routing .{HttpService , RequestContext , Route }
27
+
28
+ import scala .concurrent .{Await , ExecutionContext , Future }
29
+ import scala .util .Try
30
+
32
31
33
32
object WebApi {
34
33
val StatusKey = " status"
35
34
val ResultKey = " result"
35
+ val ResultKeyStartBytes = " {\n " .getBytes
36
+ val ResultKeyEndBytes = " }" .getBytes
37
+ val ResultKeyBytes = (" \" " + ResultKey + " \" :" ).getBytes
36
38
37
39
def badRequest (ctx : RequestContext , msg : String ) {
38
40
ctx.complete(StatusCodes .BadRequest , errMap(msg))
@@ -61,6 +63,14 @@ object WebApi {
61
63
Map (ResultKey -> result)
62
64
}
63
65
66
+ def resultToByteIterator (jobReport : Map [String , Any ], result : Iterator [_]): Iterator [_] = {
67
+ ResultKeyStartBytes .toIterator ++
68
+ (jobReport.map(t => Seq (AnyJsonFormat .write(t._1).toString(),
69
+ AnyJsonFormat .write(t._2).toString()).mkString(" :" ) ).mkString(" ," ) ++
70
+ (if (jobReport.nonEmpty) " ," else " " )).getBytes().toIterator ++
71
+ ResultKeyBytes .toIterator ++ result ++ ResultKeyEndBytes .toIterator
72
+ }
73
+
64
74
def formatException (t : Throwable ): Any =
65
75
if (t.getCause != null ) {
66
76
Map (" message" -> t.getMessage,
@@ -95,7 +105,8 @@ class WebApi(system: ActorSystem,
95
105
dataManager : ActorRef ,
96
106
supervisor : ActorRef ,
97
107
jobInfo : ActorRef )
98
- extends HttpService with CommonRoutes with DataRoutes with SJSAuthenticator with CORSSupport {
108
+ extends HttpService with CommonRoutes with DataRoutes with SJSAuthenticator with CORSSupport
109
+ with ChunkEncodedStreamingSupport {
99
110
import CommonMessages ._
100
111
import ContextSupervisor ._
101
112
import scala .concurrent .duration ._
@@ -112,6 +123,8 @@ class WebApi(system: ActorSystem,
112
123
val DefaultJobLimit = 50
113
124
val StatusKey = " status"
114
125
val ResultKey = " result"
126
+ val ResultChunkSize = Option (" spark.jobserver.result-chunk-size" ).filter(config.hasPath)
127
+ .fold(100 * 1024 )(config.getBytes(_).toInt)
115
128
116
129
val contextTimeout = SparkJobUtils .getContextTimeout(config)
117
130
val bindAddress = config.getString(" spark.jobserver.bind-address" )
@@ -228,6 +241,7 @@ class WebApi(system: ActorSystem,
228
241
*/
229
242
def contextRoutes : Route = pathPrefix(" contexts" ) {
230
243
import ContextSupervisor ._
244
+
231
245
import collection .JavaConverters ._
232
246
// user authentication
233
247
authenticate(authenticator) { authInfo =>
@@ -384,7 +398,12 @@ class WebApi(system: ActorSystem,
384
398
val resultFuture = jobInfo ? GetJobResult (jobId)
385
399
resultFuture.map {
386
400
case JobResult (_, result) =>
387
- ctx.complete(jobReport ++ resultToTable(result))
401
+ result match {
402
+ case s : Stream [_] =>
403
+ sendStreamingResponse(ctx, ResultChunkSize ,
404
+ resultToByteIterator(jobReport, s.toIterator))
405
+ case _ => ctx.complete(jobReport ++ resultToTable(result))
406
+ }
388
407
case _ =>
389
408
ctx.complete(jobReport)
390
409
}
@@ -466,7 +485,12 @@ class WebApi(system: ActorSystem,
466
485
JobManagerActor .StartJob (appName, classPath, jobConfig, events))(timeout)
467
486
respondWithMediaType(MediaTypes .`application/json`) { ctx =>
468
487
future.map {
469
- case JobResult (_, res) => ctx.complete(resultToTable(res))
488
+ case JobResult (_, res) =>
489
+ res match {
490
+ case s : Stream [_] => sendStreamingResponse(ctx, ResultChunkSize ,
491
+ resultToByteIterator(Map .empty, s.toIterator))
492
+ case _ => ctx.complete(resultToTable(res))
493
+ }
470
494
case JobErroredOut (_, _, ex) => ctx.complete(errMap(ex, " ERROR" ))
471
495
case JobStarted (jobId, context, _) =>
472
496
jobInfo ! StoreJobConfig (jobId, postedJobConfig)
0 commit comments