-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use security vocabulary for keyId doc as per solid/authentication-pan…
- Loading branch information
Showing
16 changed files
with
610 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package run.cosy | ||
|
||
import org.w3.banana.jena.Jena | ||
import akka.http.scaladsl.model.Uri | ||
/** | ||
* Set your preferred implementation of banana-rdf here. | ||
* Note: this way of setting a preferred implementation of RDF means that | ||
* all code referring to this must use one implementation of banana at compile time. | ||
* A more flexible but more tedious approach, where different parts of the code | ||
* could use different RDF implementations, and interact via translations, would require all the | ||
* code to take Rdf and ops implicit parameters `given` with the `using` keyword. | ||
* | ||
**/ | ||
object RDF { | ||
export Jena.* | ||
export Jena.given | ||
|
||
extension (uri: Uri) | ||
def toRdf = ops.URI(uri.toString) | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package run.cosy.http | ||
|
||
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, PredefinedFromEntityUnmarshallers} | ||
import akka.http.scaladsl.model.{ContentType, HttpHeader, HttpRequest, HttpResponse, StatusCode, Uri} | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
import org.w3.banana._ | ||
import org.w3.banana.syntax._ | ||
import org.w3.banana.jena.Jena | ||
import Jena._ | ||
import Jena.ops._ | ||
import akka.http.scaladsl.util.FastFuture | ||
import akka.stream.Materializer | ||
import org.apache.jena.graph.Graph | ||
import org.w3.banana.io.RDFReader | ||
|
||
import scala.util.{Failure, Try} | ||
import scala.util.control.NoStackTrace | ||
|
||
object RdfParser { | ||
import RDFMediaTypes.* | ||
|
||
/** @param base: the URI at which the document was resolved */ | ||
def rdfUnmarshaller(base: Uri): FromEntityUnmarshaller[Rdf#Graph] = | ||
//todo: this loads everything into a string - bad | ||
PredefinedFromEntityUnmarshallers.stringUnmarshaller flatMapWithInput { (entity, string) ⇒ | ||
def parseWith[T](reader: RDFReader[Rdf,Try,T]) = Future.fromTry { | ||
reader.read(new java.io.StringReader(string), base.toString) | ||
} | ||
//todo: use non blocking parsers | ||
entity.contentType.mediaType match | ||
case `text/turtle` => parseWith(turtleReader) | ||
case `application/rdf+xml` => parseWith(rdfXMLReader) | ||
case `application/n-triples` => parseWith(ntriplesReader) | ||
case `application/ld+json` => parseWith(jsonldReader) | ||
// case `text/html` => new SesameRDFaReader() | ||
case _ => FastFuture.failed(MissingParserException(string.take(400))) | ||
} | ||
|
||
def rdfRequest(uri: Uri): HttpRequest = | ||
import akka.http.scaladsl.model.headers.Accept | ||
HttpRequest(uri=uri.withoutFragment) | ||
.addHeader(Accept(`text/turtle`,`application/rdf+xml`, | ||
`application/n-triples`, | ||
`application/ld+json`.withQValue(0.8)//todo: need to update parser in banana | ||
//`text/html`.withQValue(0.2) | ||
)) //we can't specify that we want RDFa in our markup | ||
|
||
def unmarshalToRDF( | ||
resp: HttpResponse, base: Uri | ||
)(using ExecutionContext, Materializer): Future[IResponse[Rdf#Graph]] = | ||
import resp._ | ||
given FromEntityUnmarshaller[Rdf#Graph] = RdfParser.rdfUnmarshaller(base) | ||
import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller} | ||
Unmarshal(entity).to[Rdf#Graph].map { g => | ||
IResponse[Rdf#Graph](base, status, headers, entity.contentType, g) | ||
} | ||
|
||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package run.cosy.http | ||
|
||
import Web.PGWeb | ||
import akka.actor.typed.ActorSystem | ||
import akka.actor.typed.scaladsl.Behaviors | ||
import akka.http.scaladsl.Http | ||
import akka.http.scaladsl.model | ||
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller | ||
import model.{ContentType, HttpHeader, HttpRequest, HttpResponse, StatusCode, Uri} | ||
import org.apache.jena.graph.Graph | ||
import org.w3.banana.PointedGraph | ||
import run.cosy.RDF.{given, _} | ||
import akka.stream.Materializer | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
import scala.util.{Failure, Success, Try} | ||
import scala.util.control.NonFatal | ||
|
||
object Web { | ||
type PGWeb = IResponse[PointedGraph[Rdf]] | ||
|
||
extension (uri: Uri) | ||
def toRdf: Rdf#URI = ops.URI(uri.toString) | ||
|
||
} | ||
|
||
// Need to generalise this, so that it can fetch locally and from the web | ||
class Web(using val ec: ExecutionContext, val as: ActorSystem[Nothing]) { | ||
|
||
def GETRdfDoc(uri: Uri, maxRedirect: Int = 4): Future[HttpResponse] = | ||
GET(RdfParser.rdfRequest(uri), maxRedirect).map(_._1) | ||
|
||
//todo: add something to the response re number of redirects | ||
//see: https://github.com/akka/akka-http/issues/195 | ||
def GET(req: HttpRequest, maxRedirect: Int = 4, | ||
history: List[ResponseSummary] = List() | ||
// keyChain: List[Sig.Client]=List() | ||
): Future[(HttpResponse, List[ResponseSummary])] = | ||
|
||
try { | ||
import model.StatusCodes.{Success, Redirection} | ||
Http().singleRequest(req) | ||
.recoverWith { case e => Future.failed(ConnectionException(req.uri.toString, e)) } | ||
.flatMap { resp => | ||
def summary = ResponseSummary(req.uri, resp.status, resp.headers, resp.entity.contentType) | ||
|
||
resp.status match { | ||
case Success(_) => Future.successful((resp, summary :: history)) | ||
case Redirection(_) => { | ||
resp.header[model.headers.Location].map { loc => | ||
val newReq = req.withUri(loc.uri) | ||
resp.discardEntityBytes() | ||
if (maxRedirect > 0) | ||
GET(newReq, maxRedirect - 1, summary :: history) | ||
else Http().singleRequest(newReq).map((_, summary :: history)) | ||
}.getOrElse(Future.failed(HTTPException(summary, s"Location header not found on ${resp.status} for ${req.uri}"))) | ||
} | ||
//todo later: deal with authorization on remote resources | ||
// case Unauthorized => { | ||
// import akka.http.scaladsl.model.headers.{`WWW-Authenticate`,Date} | ||
// val date = Date(akka.http.scaladsl.model.DateTime.now) | ||
// val reqWithDate = req.addHeader(date) | ||
// val tryFuture = for { | ||
// wwa <- resp.header[`WWW-Authenticate`] | ||
// .fold[Try[`WWW-Authenticate`]]( | ||
// Failure(HTTPException(summary,"no WWW-Authenticate header")) | ||
// )(scala.util.Success(_)) | ||
// headers <- Try { Sig.Client.signatureHeaders(wwa).get } //<- this should always succeed | ||
// client <- keyChain.headOption.fold[Try[Sig.Client]]( | ||
// Failure(AuthException(summary,"no client keys")) | ||
// )(scala.util.Success(_)) | ||
// authorization <- client.authorize(reqWithDate,headers) | ||
// } yield { | ||
// GET(reqWithDate.addHeader(authorization), maxRedirect, summary::history, keyChain.tail) | ||
// } | ||
// Future.fromTry(tryFuture).flatten | ||
// } | ||
case _ => { | ||
resp.discardEntityBytes() | ||
Future.failed(StatusCodeException(summary)) | ||
} | ||
} | ||
} | ||
} catch { | ||
case NonFatal(e) => Future.failed(ConnectionException(req.uri.toString, e)) | ||
} | ||
end GET | ||
|
||
|
||
def GETrdf(uri: Uri): Future[IResponse[Rdf#Graph]] = | ||
GETRdfDoc(uri).flatMap(RdfParser.unmarshalToRDF(_,uri)) | ||
|
||
|
||
def pointedGET(uri: Uri): Future[PGWeb] = { | ||
import Web.* | ||
GETrdf(uri).map(_.map(PointedGraph[Rdf](uri.toRdf,_))) | ||
} | ||
} | ||
|
||
/** | ||
* Interpreted HttpResponse, i.e. the interpretation of a representation | ||
*/ | ||
case class IResponse[C](origin: Uri, status: StatusCode, | ||
headers: Seq[HttpHeader], fromContentType: ContentType, | ||
content: C) { | ||
def map[D](f: C => D) = this.copy(content=f(content)) | ||
} | ||
|
Oops, something went wrong.