blob: 8b89c733285fbdae69bb5a026c96c02286ec69cf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.s2graph.graphql
import java.nio.charset.Charset
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Route, StandardRoute}
import akka.stream.ActorMaterializer
import org.slf4j.LoggerFactory
import sangria.parser.QueryParser
import spray.json._
import scala.concurrent.Await
import scala.language.postfixOps
import scala.util._
import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller, ToResponseMarshallable}
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
import akka.util.ByteString
import sangria.ast.Document
import sangria.renderer.{QueryRenderer, QueryRendererConfig}
import scala.collection.immutable.Seq
object Server extends App {
val logger = LoggerFactory.getLogger(this.getClass)
implicit val system = ActorSystem("s2graphql-server")
implicit val materializer = ActorMaterializer()
import system.dispatcher
import scala.concurrent.duration._
import spray.json.DefaultJsonProtocol._
val graphQLServer = new GraphQLServer()
val route: Route =
get {
getFromResource("assets/graphiql.html")
} ~ (post & path("updateEdgeFetcher")) {
entity(as[JsValue]) { body =>
graphQLServer.updateEdgeFetcher(body) match {
case Success(_) => complete(StatusCodes.OK -> JsString("Update fetcher finished"))
case Failure(e) =>
logger.error("Error on execute", e)
complete(StatusCodes.InternalServerError -> spray.json.JsObject("message" -> JsString(e.toString)))
}
}
} ~ (post & path("graphql")) {
parameters('operationName.?, 'variables.?) { (operationNameParam, variablesParam) =>
entity(as[Document]) { document ⇒
variablesParam.map(parseJson) match {
case None ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationNameParam, JsObject()))
case Some(Right(js)) ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationNameParam, js.asJsObject))
case Some(Left(e))
logger.error("Error on execute", e)
complete(StatusCodes.BadRequest -> graphQLServer.formatError(e))
}
} ~ entity(as[JsValue]) { body ⇒
val fields = body.asJsObject.fields
val query = fields.get("query").map(js => js.convertTo[String])
val operationName = fields.get("operationName").filterNot(_ == JsNull).map(_.convertTo[String])
val variables = fields.get("variables").filterNot(_ == JsNull)
query.map(QueryParser.parse(_)) match {
case None ⇒ complete(StatusCodes.BadRequest -> graphQLServer.formatError("No query to execute"))
case Some(Failure(error))
logger.error("Error on execute", error)
complete(StatusCodes.BadRequest -> graphQLServer.formatError(error))
case Some(Success(document)) => variables match {
case Some(js) ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationName, js.asJsObject))
case None ⇒ complete(graphQLServer.executeGraphQLQuery(document, operationName, JsObject()))
}
}
}
}
}
val port = sys.props.get("http.port").fold(8000)(_.toInt)
logger.info(s"Starting GraphQL server... $port")
Http().bindAndHandle(route, "0.0.0.0", port).foreach { binding =>
logger.info(s"GraphQL server ready for connect")
}
def shutdown(): Unit = {
logger.info("Terminating...")
system.terminate()
Await.result(system.whenTerminated, 30 seconds)
logger.info("Terminated.")
}
// Unmarshaller
def unmarshallerContentTypes: Seq[ContentTypeRange] = mediaTypes.map(ContentTypeRange.apply)
def mediaTypes: Seq[MediaType.WithFixedCharset] =
Seq(MediaType.applicationWithFixedCharset("graphql", HttpCharsets.`UTF-8`, "graphql"))
implicit def documentMarshaller(implicit config: QueryRendererConfig = QueryRenderer.Compact): ToEntityMarshaller[Document] = {
Marshaller.oneOf(mediaTypes: _*) {
mediaType ⇒
Marshaller.withFixedContentType(ContentType(mediaType)) {
json ⇒ HttpEntity(mediaType, QueryRenderer.render(json, config))
}
}
}
implicit val documentUnmarshaller: FromEntityUnmarshaller[Document] = {
Unmarshaller.byteStringUnmarshaller
.forContentTypes(unmarshallerContentTypes: _*)
.map {
case ByteString.empty ⇒ throw Unmarshaller.NoContentException
case data ⇒
import sangria.parser.DeliveryScheme.Throw
QueryParser.parse(data.decodeString(Charset.forName("UTF-8")))
}
}
def parseJson(jsStr: String): Either[Throwable, JsValue] = {
val parsed = Try(jsStr.parseJson)
parsed match {
case Success(js) => Right(js)
case Failure(e) => Left(e)
}
}
}