Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse Accept Headers Using Antlr. #521

Merged
merged 12 commits into from
Dec 15, 2021
85 changes: 71 additions & 14 deletions src/engine/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,22 +287,79 @@ boost::asio::awaitable<void> Server::processQuery(
qet.recursivelySetTimeoutTimer(timeoutTimer);
LOG(TRACE) << qet.asString() << std::endl;

using ad_utility::MediaType;
// Determine the result media type
std::vector<MediaType> supportedMediaTypes{
ad_utility::MediaType::sparqlJson, ad_utility::MediaType::tsv,
ad_utility::MediaType::csv, ad_utility::MediaType::qleverJson};

std::string_view acceptHeader = request.base()[http::field::accept];
auto mediaType = ad_utility::findMediaTypeFromAcceptHeader(
acceptHeader, supportedMediaTypes);

// TODO<joka921> Remove this hack, as soon as the QLever-UI sends proper
// accept headers.
if (acceptHeader.empty()) {
mediaType = MediaType::qleverJson;
}

if (!mediaType.has_value()) {
co_return co_await send(
createBadRequestResponse("Could not parse any supported media type "
"from this \'Accept:\' header field: " +
std::string{acceptHeader},
request));
}

// TODO<joka921> make our own UIs use accept-headers and then remove this
// code
if (containsParam("action", "csv_export")) {
// CSV export
auto responseGenerator = composeResponseSepValues(pq, qet, ',');
auto response = createOkResponse(std::move(responseGenerator), request,
ad_utility::MediaType::csv);
co_await send(std::move(response));
mediaType = ad_utility::MediaType::csv;
} else if (containsParam("action", "tsv_export")) {
// TSV export
auto responseGenerator = composeResponseSepValues(pq, qet, '\t');
auto response = createOkResponse(std::move(responseGenerator), request,
ad_utility::MediaType::tsv);
co_await send(std::move(response));
} else {
// Normal case: JSON response
auto responseString = composeResponseJson(pq, qet, requestTimer, maxSend);
co_await sendJson(std::move(responseString));
mediaType = ad_utility::MediaType::tsv;
}

AD_CHECK(mediaType.has_value());
switch (mediaType.value()) {
break;
case ad_utility::MediaType::csv: {
auto responseGenerator = composeResponseSepValues(pq, qet, ',');
auto response = createOkResponse(std::move(responseGenerator), request,
ad_utility::MediaType::csv);
co_await send(std::move(response));
} break;
case ad_utility::MediaType::tsv: {
auto responseGenerator = composeResponseSepValues(pq, qet, '\t');
auto response = createOkResponse(std::move(responseGenerator), request,
ad_utility::MediaType::tsv);
co_await send(std::move(response));
} break;
case ad_utility::MediaType::qleverJson: {
// Normal case: JSON response
auto responseString =
composeResponseJson(pq, qet, requestTimer, maxSend);
co_await sendJson(std::move(responseString));
} break;
case ad_utility::MediaType::sparqlJson: {
// TODO<joka921> implement this, it is in a different PR which needs
// only a bit of polishing.
auto errorString =
"This endpoint currently only supports tsv, csv, and a custom "
"non-standard json format. Support for standard "
"application/sparql-results+json export will be implemented soon";
co_return co_await send(createBadRequestResponse(errorString, request));

// TODO<joka921> This will be the code when the other PR is merged.
/*
auto responseString =
composeResponseSparqlJson(pq, qet, requestTimer, maxSend);
co_await sendJson(std::move(responseString));
*/
}
default:
// This should never happen, because we have carefully restricted the
// subset of mediaTypes that can occur here.
AD_CHECK(false);
}
// Print the runtime info. This needs to be done after the query
// was computed.
Expand Down
8 changes: 6 additions & 2 deletions src/util/HttpServer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
add_library(httpServer HttpServer.h HttpUtils.h UrlParser.h UrlParser.cpp MediaTypes.h MediaTypes.cpp)
target_link_libraries(httpServer ${ICU_LIBRARIES})
add_subdirectory(HttpParser)
add_library(mediaTypes MediaTypes.h MediaTypes.cpp)
target_link_libraries(mediaTypes ${ICU_LIBRARIES} absl::flat_hash_map)
add_library(httpServer HttpServer.h HttpUtils.h UrlParser.h UrlParser.cpp "HttpParser/AcceptHeaderQleverVisitor.h")

target_link_libraries(httpServer mediaTypes httpParser ${ICU_LIBRARIES})
5 changes: 5 additions & 0 deletions src/util/HttpServer/HttpParser/AcceptHeaderQleverVisitor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2021, University of Freiburg,
// Chair of Algorithms and Data Structures.
// Author: Johannes Kalmbach <kalmbach@cs.uni-freiburg.de>

#include "./AcceptHeaderQleverVisitor.h"
166 changes: 166 additions & 0 deletions src/util/HttpServer/HttpParser/AcceptHeaderQleverVisitor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright 2021, University of Freiburg,
// Chair of Algorithms and Data Structures.
// Author: Johannes Kalmbach <kalmbach@cs.uni-freiburg.de>

#ifndef QLEVER_ACCEPTHEADERQLEVERVISITOR_H
#define QLEVER_ACCEPTHEADERQLEVERVISITOR_H

// Generated from AcceptHeader.g4 by ANTLR 4.9.2

#include "../MediaTypes.h"
#include "./generated/AcceptHeaderVisitor.h"
#include "antlr4-runtime.h"

/**
* This class provides an empty implementation of AcceptHeaderVisitor, which can
* be extended to create a visitor which only needs to handle a subset of the
* available methods.
*/
class AcceptHeaderQleverVisitor : public AcceptHeaderVisitor {
public:
using MediaType = ad_utility::MediaType;

class Exception : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class ParseException : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class NotSupportedException : public std::exception {
public:
explicit NotSupportedException(std::string_view featureName)
: _message{"The feature \"" + std::string{featureName} +
"\" is currently not supported by this parser"} {}
[[nodiscard]] const char* what() const noexcept override {
return _message.c_str();
}

private:
std::string _message;
};

antlrcpp::Any visitAcceptWithEof(
AcceptHeaderParser::AcceptWithEofContext* ctx) override {
return ctx->accept()->accept(this);
}

antlrcpp::Any visitAccept(AcceptHeaderParser::AcceptContext* ctx) override {
std::vector<ad_utility::MediaTypeWithQuality> acceptedMediaTypes;
for (const auto& child : ctx->rangeAndParams()) {
auto mediaType =
child->accept(this)
.as<std::optional<ad_utility::MediaTypeWithQuality>>();
if (mediaType.has_value()) {
acceptedMediaTypes.push_back(mediaType.value());
}
if (acceptedMediaTypes.empty()) {
throw Exception{
"Not a single media type known to this parser was detected in \"" +
ctx->getText() + '\"'};
}
}
return {std::move(acceptedMediaTypes)};
}

antlrcpp::Any visitRangeAndParams(
AcceptHeaderParser::RangeAndParamsContext* ctx) override {
std::optional<ad_utility::MediaTypeWithQuality> result = std::nullopt;
float quality = 1.0;
if (ctx->acceptParams()) {
quality = ctx->acceptParams()->accept(this).as<float>();
}
using V = std::optional<ad_utility::MediaTypeWithQuality::Variant>;
auto mediaRange = ctx->mediaRange()->accept(this).as<V>();
if (mediaRange.has_value()) {
result.emplace(ad_utility::MediaTypeWithQuality{
quality, std::move(mediaRange.value())});
}
return result;
}

antlrcpp::Any visitMediaRange(
AcceptHeaderParser::MediaRangeContext* ctx) override {
using V = std::optional<ad_utility::MediaTypeWithQuality::Variant>;
if (!ctx->subtype()) {
if (!ctx->type()) {
return V{ad_utility::MediaTypeWithQuality::Wildcard{}};
} else {
return V{ad_utility::MediaTypeWithQuality::TypeWithWildcard{
ctx->type()->getText()}};
}
}
if (!ctx->parameter().empty()) {
throw NotSupportedException{"Media type parameters like \"charset=...\""};
}
return V{ad_utility::toMediaType(ctx->getText())};
}

antlrcpp::Any visitType(AcceptHeaderParser::TypeContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitSubtype(AcceptHeaderParser::SubtypeContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitAcceptParams(
AcceptHeaderParser::AcceptParamsContext* ctx) override {
if (!ctx->acceptExt().empty()) {
throw NotSupportedException{"Media type parameters like \"charset=...\""};
}
return ctx->weight()->accept(this);
}

antlrcpp::Any visitWeight(AcceptHeaderParser::WeightContext* ctx) override {
auto throwException = [ctx] {
throw ParseException{
"Decimal values for quality parameters in accept header must be "
"between 0 and 1, and must have at most 3 decimal digits. Found "
"illegal quality value " +
ctx->qvalue()->getText()};
};
if (ctx->qvalue()->getText().size() > 5) {
throwException();
}
auto quality = std::stof(ctx->qvalue()->getText());
if (quality > 1.0f) {
throwException();
}
return quality;
}

antlrcpp::Any visitQvalue(AcceptHeaderParser::QvalueContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitAcceptExt(
AcceptHeaderParser::AcceptExtContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitParameter(
AcceptHeaderParser::ParameterContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitToken(AcceptHeaderParser::TokenContext* ctx) override {
return ctx->getText();
return visitChildren(ctx);
}

antlrcpp::Any visitTchar(AcceptHeaderParser::TcharContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitQuotedString(
AcceptHeaderParser::QuotedStringContext* ctx) override {
return visitChildren(ctx);
}

antlrcpp::Any visitQuoted_pair(
AcceptHeaderParser::Quoted_pairContext* ctx) override {
return visitChildren(ctx);
}
};

#endif // QLEVER_ACCEPTHEADERQLEVERVISITOR_H
7 changes: 7 additions & 0 deletions src/util/HttpServer/HttpParser/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
add_library(httpParser AcceptHeaderQleverVisitor.h AcceptHeaderQleverVisitor.cpp
generated/AcceptHeaderBaseListener.h generated/AcceptHeaderBaseListener.cpp
generated/AcceptHeaderLexer.h generated/AcceptHeaderLexer.cpp
generated/AcceptHeaderListener.h generated/AcceptHeaderListener.cpp
generated/AcceptHeaderParser.h generated/AcceptHeaderParser.cpp
generated/AcceptHeaderVisitor.h generated/AcceptHeaderVisitor.cpp)
target_link_libraries(httpParser antlr4_static)