All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.greenfossil.thorium.Request.scala Maven / Gradle / Ivy

/*
 * Copyright 2022 Greenfossil Pte Ltd
 *
 * Licensed 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 com.greenfossil.thorium

import com.greenfossil.commons.LocaleUtil
import com.greenfossil.commons.json.{JsValue, Json}
import com.greenfossil.thorium.decorators.CSRFGuardModule
import com.linecorp.armeria.common.*
import com.linecorp.armeria.server.{ProxiedAddresses, ServiceRequestContext}
import org.slf4j.LoggerFactory

import java.net.{InetAddress, InetSocketAddress}
import java.util.Locale
import java.util.Locale.LanguageRange
import java.util.concurrent.CompletableFuture
import java.util.stream.Collectors
import scala.util.Try

private[thorium] val requestLogger = LoggerFactory.getLogger("http.request")

object Request:
  
  def actionResponder(request: Request, actionResponseFn: Request => ActionResponse): ActionResponse =
    actionResponseFn(request)

trait Request(val requestContext: ServiceRequestContext, 
              val aggregatedHttpRequest: AggregatedHttpRequest) extends com.greenfossil.commons.LocaleProvider:

  import scala.jdk.CollectionConverters.*

  def config: Configuration = requestContext.attr(RequestAttrs.Config)

  def requestTZ = requestContext.attr(RequestAttrs.TZ)

  def session: Session = requestContext.attr(RequestAttrs.Session)

  def flash: Flash = requestContext.attr(RequestAttrs.Flash)

  def recaptchaResponse: Recaptcha = requestContext.attr(RequestAttrs.RecaptchaResponse)

  def csrfTokenName: String = config.httpConfiguration.csrfConfig.cookieName

  /*
   * Create a CSRFToken, and and set attr RequestAttrs.CSRFToken
   * Every request can have only 1 csrf-token
   */
  lazy val csrfToken: String =
    val token = CSRFGuardModule.generateCSRFToken(using this)
    requestContext.setAttr(RequestAttrs.CSRFToken, token)
    token

  def env: Environment = config.environment

  def httpConfiguration: HttpConfiguration = config.httpConfiguration

  def contentType: MediaType = requestContext.request().contentType()

  def queryParam(param: String): Option[String] = Option(requestContext.queryParam(param))

  def queryParams(param: String): List[String] = requestContext.queryParams(param).asScala.toList
  
  def pathParam(name: String) = requestContext.pathParam(name)
  
  def pathParams: Map[String, String] = requestContext.pathParams().asScala.toMap

  def isXhr: Boolean =
    // Check header key and value if XHR (case insensitive)
    requestContext.request().headers().contains("X-Requested-With","XMLHttpRequest" )

  def queryParams: QueryParams = requestContext.queryParams()

  def queryString: String = queryParams.toQueryString

  def queryParamsList: List[(String, String)] =
    queryParams.stream()
      .map(e => e.getKey -> e.getValue)
      .collect(Collectors.toList[(String, String)])
      .asScala
      .toList

  def remoteAddress: InetAddress =
    requestContext.remoteAddress().getAddress

  def secure: Boolean = "https".equalsIgnoreCase(uriScheme)

  def uri: java.net.URI = requestContext.uri()

  //TOOD - need to test
  def host: String =
    getHeader(HttpHeaderNames.HOST).getOrElse(uri.getHost)

  def port: Int = uri.getPort

  /**
    * The host name and port number (if there is any)
    *
    */
  def uriAuthority: String = uri.getAuthority

  /**
    * URI scheme (e.g. http, https)
    */
  def uriScheme: String = uri.getScheme

  /**
   *
   * @return - path, which does not include query string
   */
  def path: String = requestContext.path()

  /**
   *
   * @return - path and its query string if exists
   */
  def pathAndQueryString: String = aggregatedHttpRequest.path()

  def endpoint: Endpoint = Endpoint(path)

  def headers: RequestHeaders = requestContext.request().headers()

  def getHeader(name: CharSequence): Option[String] = Option(headers.get(name))

  def getHeaderAll(name: CharSequence): List[String] = headers.getAll(name).asScala.toList

  //https://www.javatips.net/api/java.util.locale.languagerange
  def acceptLanguages: Seq[LanguageRange] =
    Option(requestContext.request().acceptLanguages()).map(_.asScala.toSeq).getOrElse(Nil)

  lazy val cookies: Set[Cookie] =
    requestContext.request().headers().cookies().asInstanceOf[java.util.Set[Cookie]].asScala.toSet

  def findCookie(name: String): Option[Cookie] =
    cookies.find(c => c.name() == name)

  def clientAddress: InetAddress = requestContext.clientAddress()

  def proxiedAddresses: ProxiedAddresses = requestContext.proxiedAddresses()

  def proxiedDestinationAddresses: Seq[InetSocketAddress] = proxiedAddresses.destinationAddresses().asScala.toSeq

  def proxiedSourceAddress: InetSocketAddress = proxiedAddresses.sourceAddress()

  def refererOpt: Option[String] =
    getHeader("X-Alt-Referer")
      .orElse(getHeader("referer"))

  def method: HttpMethod = requestContext.method()

  def userAgent: Option[String] = Option(headers.get(HttpHeaderNames.USER_AGENT))

  def authorization: Option[String] = Option(headers.get(HttpHeaderNames.AUTHORIZATION))

  def availableLanguages: Seq[Locale] =
    Try(config.config.getStringList("app.i18n.langs").asScala.toList.map(Locale.forLanguageTag))
      .getOrElse(Seq(Locale.getDefault))

  def localeVariantOpt: Option[String] = Try(config.config.getString("app.i18n.variant")).toOption

  def locale: Locale = LocaleUtil.getBestMatchLocale(acceptLanguages, availableLanguages, localeVariantOpt)

  def asText: String = aggregatedHttpRequest.contentUtf8()

  //application/json
  def asJson: JsValue = Json.parse(asText)

  //application/x-www-form-urlencoded
  def asFormUrlEncoded: FormUrlEndcoded =
    FormUrlEncodedParser.parse(asText)

  //MultiPart
  import com.linecorp.armeria.common.multipart.Multipart
  private def asMultipartFormData: CompletableFuture[MultipartFormData] =
    actionLogger.debug(s"Processing private asMultipartFormData.")
    Multipart.from(aggregatedHttpRequest.toHttpRequest)
      .aggregate()
      .thenApply(mp =>
        actionLogger.debug(s"Getting Multipart Response.")
        MultipartFormData(mp, requestContext.config().multipartUploadsLocation())
      )
    
  def asMultipartFormData(fn: MultipartFormData => ActionResponse): ActionResponse =
    actionLogger.debug(s"Processing asMultipartFormData.")
    val resp = asMultipartFormData.thenApply(fn(_)).get
    actionLogger.debug(s"Return action response:$resp")
    resp

  //Raw Buffer - TODO - testcase needed and check for conformance
  def asRaw: HttpData = aggregatedHttpRequest.content()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy