Skip to content

Commit

Permalink
feat(lapis2): support url encoded form POST requests
Browse files Browse the repository at this point in the history
This adds support for using LAPIS via plain HTML forms with `method="POST"` and content type `application/x-www-form-urlencoded`

resolves #754
  • Loading branch information
fengelniederhammer committed Apr 30, 2024
1 parent ac1d719 commit f2f62b1
Show file tree
Hide file tree
Showing 24 changed files with 1,286 additions and 705 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ abstract class DataOpennessAuthorizationFilter(protected val objectMapper: Objec
}

private fun makeRequestBodyReadableMoreThanOnce(request: HttpServletRequest) =
CachedBodyHttpServletRequest(request, objectMapper)
CachedBodyHttpServletRequest.from(request, objectMapper)

abstract fun isAuthorizedForEndpoint(request: CachedBodyHttpServletRequest): AuthorizationResult
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class CompressionFilter(val objectMapper: ObjectMapper, val requestCompression:
response: HttpServletResponse,
filterChain: FilterChain,
) {
val reReadableRequest = CachedBodyHttpServletRequest(request, objectMapper)
val reReadableRequest = CachedBodyHttpServletRequest.from(request, objectMapper)

val compressionPropertyInRequest = try {
getValidatedCompressionProperty(reReadableRequest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class DataFormatParameterFilter(val objectMapper: ObjectMapper) : OncePerRequest
response: HttpServletResponse,
filterChain: FilterChain,
) {
val reReadableRequest = CachedBodyHttpServletRequest(request, objectMapper)
val reReadableRequest = CachedBodyHttpServletRequest.from(request, objectMapper)

val requestWithModifiedAcceptHeader = HeaderModifyingRequestWrapper(
reReadableRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class DownloadAsFileFilter(
response: HttpServletResponse,
filterChain: FilterChain,
) {
val reReadableRequest = CachedBodyHttpServletRequest(request, objectMapper)
val reReadableRequest = CachedBodyHttpServletRequest.from(request, objectMapper)

val downloadAsFile = reReadableRequest.getBooleanField(DOWNLOAD_AS_FILE_PROPERTY) ?: false
if (downloadAsFile) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.genspectrum.lapis.controller

import com.fasterxml.jackson.databind.ObjectMapper
import org.genspectrum.lapis.util.tryToGuessTheType
import org.springframework.http.HttpInputMessage
import org.springframework.http.HttpOutputMessage
import org.springframework.http.MediaType
import org.springframework.http.converter.AbstractHttpMessageConverter
import org.springframework.http.converter.FormHttpMessageConverter
import org.springframework.stereotype.Component

@Component
class JacksonFormHttpMessageConverter(
private val objectMapper: ObjectMapper,
) : AbstractHttpMessageConverter<Any>(MediaType.APPLICATION_FORM_URLENCODED) {
private val formHttpMessageConverter = FormHttpMessageConverter()

override fun canWrite(
clazz: Class<*>,
mediaType: MediaType?,
): Boolean {
return false
}

override fun supports(clazz: Class<*>): Boolean {
return true
}

override fun writeInternal(
t: Any,
outputMessage: HttpOutputMessage,
) {
throw NotImplementedError("This class should never need to write.")
}

override fun readInternal(
clazz: Class<out Any>,
inputMessage: HttpInputMessage,
): Any {
val multiValueMap = formHttpMessageConverter.read(null, inputMessage)

val mapValues = multiValueMap.mapValues(::tryToGuessTheType)

val json = objectMapper.writeValueAsString(mapValues)

return objectMapper.readValue(json, clazz)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, TAB, siloQueryModel::getAggregated)
}

@PostMapping(AGGREGATED_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(
AGGREGATED_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisAggregatedResponse
@Operation(
operationId = "postAggregated",
Expand All @@ -259,7 +263,11 @@ class LapisController(
return LapisResponse(siloQueryModel.getAggregated(request).toList())
}

@PostMapping(AGGREGATED_ROUTE, produces = [TEXT_CSV_VALUE])
@PostMapping(
AGGREGATED_ROUTE,
produces = [TEXT_CSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = AGGREGATED_ENDPOINT_DESCRIPTION,
operationId = "postAggregatedAsCsv",
Expand All @@ -274,7 +282,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, COMMA, siloQueryModel::getAggregated)
}

@PostMapping(AGGREGATED_ROUTE, produces = [TEXT_TSV_VALUE])
@PostMapping(
AGGREGATED_ROUTE,
produces = [TEXT_TSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = AGGREGATED_ENDPOINT_DESCRIPTION,
operationId = "postAggregatedAsTsv",
Expand Down Expand Up @@ -452,7 +464,11 @@ class LapisController(
)
}

@PostMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(
NUCLEOTIDE_MUTATIONS_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisNucleotideMutationsResponse
@Operation(
operationId = "postNucleotideMutations",
Expand All @@ -468,7 +484,11 @@ class LapisController(
return LapisResponse(result.toList())
}

@PostMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [TEXT_CSV_VALUE])
@PostMapping(
NUCLEOTIDE_MUTATIONS_ROUTE,
produces = [TEXT_CSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = NUCLEOTIDE_MUTATION_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideMutationsAsCsv",
Expand All @@ -489,7 +509,11 @@ class LapisController(
)
}

@PostMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [TEXT_TSV_VALUE])
@PostMapping(
NUCLEOTIDE_MUTATIONS_ROUTE,
produces = [TEXT_TSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = NUCLEOTIDE_MUTATION_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideMutationsAsTsv",
Expand Down Expand Up @@ -669,7 +693,11 @@ class LapisController(
)
}

@PostMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(
AMINO_ACID_MUTATIONS_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisAminoAcidMutationsResponse
@Operation(
operationId = "postAminoAcidMutations",
Expand All @@ -685,7 +713,11 @@ class LapisController(
return LapisResponse(result.toList())
}

@PostMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [TEXT_CSV_VALUE])
@PostMapping(
AMINO_ACID_MUTATIONS_ROUTE,
produces = [TEXT_CSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = AMINO_ACID_MUTATIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidMutationsAsCsv",
Expand All @@ -708,7 +740,11 @@ class LapisController(
)
}

@PostMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [TEXT_TSV_VALUE])
@PostMapping(
AMINO_ACID_MUTATIONS_ROUTE,
produces = [TEXT_TSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = AMINO_ACID_MUTATIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidMutationsAsCsv",
Expand Down Expand Up @@ -882,7 +918,11 @@ class LapisController(
return writeCsvToResponse(response, request, httpHeaders.accept, TAB, siloQueryModel::getDetails)
}

@PostMapping(DETAILS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(
DETAILS_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisDetailsResponse
@Operation(
operationId = "postDetails",
Expand All @@ -897,7 +937,11 @@ class LapisController(
return LapisResponse(siloQueryModel.getDetails(request).toList())
}

@PostMapping(DETAILS_ROUTE, produces = [TEXT_CSV_VALUE])
@PostMapping(
DETAILS_ROUTE,
produces = [TEXT_CSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = DETAILS_ENDPOINT_DESCRIPTION,
operationId = "postDetailsAsCsv",
Expand All @@ -912,7 +956,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, COMMA, siloQueryModel::getDetails)
}

@PostMapping(DETAILS_ROUTE, produces = [TEXT_TSV_VALUE])
@PostMapping(
DETAILS_ROUTE,
produces = [TEXT_TSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = DETAILS_ENDPOINT_DESCRIPTION,
operationId = "postDetailsAsTsv",
Expand Down Expand Up @@ -1079,7 +1127,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, TAB, siloQueryModel::getNucleotideInsertions)
}

@PostMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(
NUCLEOTIDE_INSERTIONS_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisNucleotideInsertionsResponse
@Operation(
operationId = "postNucleotideInsertions",
Expand All @@ -1095,7 +1147,11 @@ class LapisController(
return LapisResponse(result.toList())
}

@PostMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [TEXT_CSV_VALUE])
@PostMapping(
NUCLEOTIDE_INSERTIONS_ROUTE,
produces = [TEXT_CSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = NUCLEOTIDE_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideInsertionsAsCsv",
Expand All @@ -1112,7 +1168,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, COMMA, siloQueryModel::getNucleotideInsertions)
}

@PostMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [TEXT_TSV_VALUE])
@PostMapping(
NUCLEOTIDE_INSERTIONS_ROUTE,
produces = [TEXT_TSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = NUCLEOTIDE_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideInsertionsAsTsv",
Expand Down Expand Up @@ -1279,7 +1339,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, TAB, siloQueryModel::getAminoAcidInsertions)
}

@PostMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PostMapping(
AMINO_ACID_INSERTIONS_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisAminoAcidInsertionsResponse
@Operation(
operationId = "postAminoAcidInsertions",
Expand All @@ -1295,7 +1359,11 @@ class LapisController(
return LapisResponse(result.toList())
}

@PostMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [TEXT_CSV_VALUE])
@PostMapping(
AMINO_ACID_INSERTIONS_ROUTE,
produces = [TEXT_CSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidInsertionsAsCsv",
Expand All @@ -1310,7 +1378,11 @@ class LapisController(
writeCsvToResponse(response, request, httpHeaders.accept, COMMA, siloQueryModel::getAminoAcidInsertions)
}

@PostMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [TEXT_TSV_VALUE])
@PostMapping(
AMINO_ACID_INSERTIONS_ROUTE,
produces = [TEXT_TSV_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@StringResponseOperation(
description = AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidInsertionsAsTsv",
Expand Down Expand Up @@ -1374,7 +1446,11 @@ class LapisController(
.writeFastaTo(response, dataVersion)
}

@PostMapping("$ALIGNED_AMINO_ACID_SEQUENCES_ROUTE/{gene}", produces = [TEXT_X_FASTA_VALUE])
@PostMapping(
"$ALIGNED_AMINO_ACID_SEQUENCES_ROUTE/{gene}",
produces = [TEXT_X_FASTA_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisAlignedAminoAcidSequenceResponse
fun postAlignedAminoAcidSequence(
@PathVariable(name = "gene", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.genspectrum.lapis.response.writeFastaTo
import org.genspectrum.lapis.silo.DataVersion
import org.genspectrum.lapis.silo.SequenceType
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
Expand Down Expand Up @@ -102,7 +103,11 @@ class MultiSegmentedSequenceController(
.writeFastaTo(response, dataVersion)
}

@PostMapping("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/{segment}", produces = [TEXT_X_FASTA_VALUE])
@PostMapping(
"$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/{segment}",
produces = [TEXT_X_FASTA_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisAlignedMultiSegmentedNucleotideSequenceResponse
fun postAlignedNucleotideSequence(
@PathVariable(name = "segment", required = true)
Expand Down Expand Up @@ -176,7 +181,11 @@ class MultiSegmentedSequenceController(
.writeFastaTo(response, dataVersion)
}

@PostMapping("$UNALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/{segment}", produces = [TEXT_X_FASTA_VALUE])
@PostMapping(
"$UNALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/{segment}",
produces = [TEXT_X_FASTA_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisUnalignedMultiSegmentedNucleotideSequenceResponse
fun postUnalignedNucleotideSequence(
@PathVariable(name = "segment", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.genspectrum.lapis.response.writeFastaTo
import org.genspectrum.lapis.silo.DataVersion
import org.genspectrum.lapis.silo.SequenceType
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand Down Expand Up @@ -99,7 +100,11 @@ class SingleSegmentedSequenceController(
.writeFastaTo(response, dataVersion)
}

@PostMapping(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE, produces = [TEXT_X_FASTA_VALUE])
@PostMapping(
ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE,
produces = [TEXT_X_FASTA_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisAlignedSingleSegmentedNucleotideSequenceResponse
fun postAlignedNucleotideSequence(
@Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_SEQUENCE_REQUEST_SCHEMA"))
Expand Down Expand Up @@ -167,7 +172,11 @@ class SingleSegmentedSequenceController(
.writeFastaTo(response, dataVersion)
}

@PostMapping(UNALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE, produces = [TEXT_X_FASTA_VALUE])
@PostMapping(
UNALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE,
produces = [TEXT_X_FASTA_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
)
@LapisUnalignedSingleSegmentedNucleotideSequenceResponse
fun postUnalignedNucleotideSequence(
@Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_SEQUENCE_REQUEST_SCHEMA"))
Expand Down
Loading

0 comments on commit f2f62b1

Please sign in to comment.