mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 19:16:53 +00:00
feature/Handle request header If-Modified-Since 2
This commit is contained in:
parent
70e51ca71e
commit
9f120c236b
@ -57,6 +57,7 @@ import code.bankattribute.BankAttribute
|
||||
import code.bankconnectors.storedprocedure.StoredProceduresMockedData
|
||||
import code.bankconnectors.{Connector, ConnectorEndpoints}
|
||||
import code.branches.MappedBranch
|
||||
import code.etag.MappedCache
|
||||
import code.cardattribute.MappedCardAttribute
|
||||
import code.cards.{MappedPhysicalCard, PinReset}
|
||||
import code.connectormethod.ConnectorMethod
|
||||
@ -1011,6 +1012,7 @@ object ToSchemify {
|
||||
// The following tables are accessed directly via Mapper / JDBC
|
||||
val models: List[MetaMapper[_]] = List(
|
||||
AuthUser,
|
||||
MappedCache,
|
||||
AtmAttribute,
|
||||
Admin,
|
||||
MappedBank,
|
||||
|
||||
@ -32,7 +32,7 @@ import java.net.URLDecoder
|
||||
import java.nio.charset.Charset
|
||||
import java.text.{ParsePosition, SimpleDateFormat}
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.{Calendar, Date, UUID}
|
||||
import java.util.{Calendar, Date, TimeZone, UUID}
|
||||
|
||||
import code.UserRefreshes.UserRefreshes
|
||||
import code.accountholders.AccountHolders
|
||||
@ -115,7 +115,9 @@ import org.apache.commons.lang3.StringUtils
|
||||
import java.security.AccessControlException
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import code.etag.MappedCache
|
||||
import code.users.Users
|
||||
import net.liftweb.mapper.By
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
|
||||
@ -487,22 +489,83 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
// If the resource has not been modified since, the response is a 304 without any body;
|
||||
// the Last-Modified response header of a previous request contains the date of last modification
|
||||
private def checkIfModifiedSinceHeader(cc: Option[CallContext], httpCode: Int, httpBody: Box[String], headerValue: String): Int = {
|
||||
if(httpCode == 200) // If-Modified-Since can only be used with a GET or HEAD
|
||||
httpCode // TODO Implement If-Modified-Since request HTTP header behaviour
|
||||
else
|
||||
httpCode
|
||||
def headerValueToMillis(): Long = {
|
||||
var epochTime = 0L
|
||||
// Create a DateFormat and set the timezone to GMT.
|
||||
val df: SimpleDateFormat = new SimpleDateFormat(DateWithSeconds)
|
||||
// df.setTimeZone(TimeZone.getTimeZone("GMT"))
|
||||
try { // Convert string into Date, for instance: "2023-05-19T02:31:05Z"
|
||||
epochTime = df.parse(headerValue).getTime()
|
||||
} catch {
|
||||
case e: ParseException => e.printStackTrace
|
||||
}
|
||||
epochTime
|
||||
}
|
||||
|
||||
def asyncUpdate(row: MappedCache, hash: String): Future[Boolean] = {
|
||||
Future { // Async update
|
||||
row
|
||||
.LastUpdatedEpochTime(System.currentTimeMillis)
|
||||
.CacheValue(hash)
|
||||
.save
|
||||
}
|
||||
}
|
||||
|
||||
def asyncCreate(cacheKey: String, hash: String): Future[Boolean] = {
|
||||
Future { // Async create
|
||||
tryo(MappedCache.create
|
||||
.CacheKey(cacheKey)
|
||||
.CacheValue(hash)
|
||||
.LastUpdatedEpochTime(System.currentTimeMillis)
|
||||
.CacheNamespace("ETag")
|
||||
.save) match {
|
||||
case Full(value) => value
|
||||
case other =>
|
||||
logger.debug(other)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val url = cc.map(_.url).getOrElse("")
|
||||
val hashedRequestPayload = HashUtil.Sha256Hash(url)
|
||||
val cacheKey = cc.map(i => s"""${i.consumer.map(_.consumerId.get).getOrElse("")}::${i.userId}::${hashedRequestPayload}""")
|
||||
.getOrElse(hashedRequestPayload)
|
||||
val hash = HashUtil.Sha256Hash(s"${url}${httpBody.getOrElse("")}")
|
||||
|
||||
if(httpCode == 200) { // If-Modified-Since can only be used with a GET or HEAD
|
||||
val validETag = MappedCache.find(By(MappedCache.CacheKey, cacheKey), By(MappedCache.CacheNamespace, ResponseHeader.ETag)) match {
|
||||
case Full(row) if row.lastUpdatedEpochTime < headerValueToMillis() =>
|
||||
val modified = row.cacheValue != hash
|
||||
if(modified) {
|
||||
asyncUpdate(row, hash)
|
||||
false // ETAg is outdated
|
||||
} else {
|
||||
true // ETAg is up to date
|
||||
}
|
||||
case Empty =>
|
||||
asyncCreate(cacheKey, hash)
|
||||
false // There is no ETAg at all
|
||||
case _ =>
|
||||
false // In case of any issue we consider ETAg as outdated
|
||||
}
|
||||
if (validETag) // Response has not been changed since our previous call
|
||||
304
|
||||
else
|
||||
httpCode
|
||||
} else httpCode
|
||||
}
|
||||
|
||||
private def checkConditionalRequest(cc: Option[CallContext], httpCode: Int, httpBody: Box[String]) = {
|
||||
val requestHeaders: List[HTTPParam] = cc.map(_.requestHeaders).getOrElse(Nil)
|
||||
requestHeaders.filter(_.name == RequestHeader.`If-None-Match` ).headOption match {
|
||||
case Some(value) =>
|
||||
case Some(value) => // Handle the If-None-Match HTTP request header
|
||||
checkIfNotMatchHeader(cc, httpCode, httpBody, value.values.mkString(""))
|
||||
case None =>
|
||||
// When used in combination with If-None-Match, it is ignored, unless the server doesn't support If-None-Match.
|
||||
// The most common use case is to update a cached entity that has no associated ETag
|
||||
requestHeaders.filter(_.name == RequestHeader.`If-Modified-Since` ).headOption match {
|
||||
case Some(value) =>
|
||||
case Some(value) => // Handle the If-Modified-Since HTTP request header
|
||||
checkIfModifiedSinceHeader(cc, httpCode, httpBody, value.values.mkString(""))
|
||||
case None =>
|
||||
httpCode
|
||||
|
||||
30
obp-api/src/main/scala/code/etag/MappedCache.scala
Normal file
30
obp-api/src/main/scala/code/etag/MappedCache.scala
Normal file
@ -0,0 +1,30 @@
|
||||
package code.etag
|
||||
|
||||
import net.liftweb.mapper._
|
||||
|
||||
class MappedCache extends MappedCacheTrait with LongKeyedMapper[MappedCache] with IdPK {
|
||||
|
||||
def getSingleton = MappedCache
|
||||
|
||||
object CacheKey extends MappedString(this, 1000)
|
||||
object CacheValue extends MappedString(this, 1000)
|
||||
object CacheNamespace extends MappedString(this, 200)
|
||||
object LastUpdatedEpochTime extends MappedLong(this)
|
||||
|
||||
override def cacheKey: String = CacheKey.get
|
||||
override def cacheValue: String = CacheValue.get
|
||||
override def cacheNamespace: String = CacheNamespace.get
|
||||
override def lastUpdatedEpochTime: Long = LastUpdatedEpochTime.get
|
||||
}
|
||||
|
||||
object MappedCache extends MappedCache with LongKeyedMetaMapper[MappedCache] {
|
||||
override def dbTableName = "Cache" // define the DB table name
|
||||
override def dbIndexes: List[BaseIndex[MappedCache]] = UniqueIndex(CacheKey) :: super.dbIndexes
|
||||
}
|
||||
|
||||
trait MappedCacheTrait {
|
||||
def cacheKey: String
|
||||
def cacheValue: String
|
||||
def cacheNamespace: String
|
||||
def lastUpdatedEpochTime: Long
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user