Skip to content

Commit

Permalink
add html2pdf and markdown2pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
klahap committed Sep 10, 2024
1 parent 74dd4b9 commit 3d5f42d
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 21 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
kotlin("plugin.serialization") version "2.0.10"
}

group = "io.github.klahap"
group = "io.github.goquati"
version = "0.0.1"

project.group
Expand Down Expand Up @@ -55,6 +55,7 @@ kotlin {
}

dependencies {
implementation("org.jetbrains:markdown:0.7.3")
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.3.0")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.klahap
package io.github.goquati

import de.smart.nexus.orchestrator.oas_model.ErrorResponseDto
import org.springframework.http.HttpStatus
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.klahap
package io.github.goquati

import org.slf4j.Logger
import org.slf4j.LoggerFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.klahap
package io.github.goquati

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/io/github/goquati/api/Html2PdfController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.github.goquati.api

import de.smart.nexus.orchestrator.api.Html2pdfApi
import de.smart.nexus.orchestrator.oas_model.Html2PdfRequestDto
import io.github.goquati.LoggerDelegate
import io.github.goquati.LoggerDelegate.Companion.measureExecutionTime
import io.github.goquati.service.Html2PdfService
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import java.util.*

@Controller
class Html2PdfController(
private val html2PdfService: Html2PdfService,
) : Html2pdfApi {
override suspend fun convertHtml2Pdf(
html2PdfRequestDto: Html2PdfRequestDto,
serverHttpRequest: ServerHttpRequest
): ResponseEntity<Resource> {
return log.measureExecutionTime("generated PDF") {
html2PdfService.generatePdf(
data = html2PdfRequestDto.data,
options = html2PdfRequestDto.options,
)
}.let { ResponseEntity.ok(ByteArrayResource(it)) }
}

@GetMapping("/html2pdf-data/{id}", produces = [MediaType.TEXT_HTML_VALUE])
suspend fun getHtml(@PathVariable id: UUID) = ResponseEntity.ok(html2PdfService.getHtml(id))

companion object {
private val log by LoggerDelegate()
}
}
33 changes: 33 additions & 0 deletions src/main/kotlin/io/github/goquati/api/Markdown2PdfController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.goquati.api

import de.smart.nexus.orchestrator.api.Markdown2pdfApi
import de.smart.nexus.orchestrator.oas_model.Markdown2PdfRequestDto
import io.github.goquati.LoggerDelegate
import io.github.goquati.LoggerDelegate.Companion.measureExecutionTime
import io.github.goquati.service.Markdown2PdfService
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.http.ResponseEntity
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.stereotype.Controller

@Controller
class Markdown2PdfController(
private val markdown2PdfService: Markdown2PdfService,
) : Markdown2pdfApi {
override suspend fun convertMarkdown2Pdf(
markdown2PdfRequestDto: Markdown2PdfRequestDto,
serverHttpRequest: ServerHttpRequest
): ResponseEntity<Resource> {
return log.measureExecutionTime("generated PDF") {
markdown2PdfService.generatePdf(
data = markdown2PdfRequestDto.data,
options = markdown2PdfRequestDto.options,
)
}.let { ResponseEntity.ok(ByteArrayResource(it)) }
}

companion object {
private val log by LoggerDelegate()
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.github.klahap.api
package io.github.goquati.api

import de.smart.nexus.orchestrator.api.Web2pdfApi
import de.smart.nexus.orchestrator.oas_model.PdfPrintOptionsDto
import de.smart.nexus.orchestrator.oas_model.Web2PdfRequestDto
import io.github.klahap.LoggerDelegate
import io.github.klahap.LoggerDelegate.Companion.measureExecutionTime
import io.github.klahap.service.Web2PdfService
import io.github.klahap.service.Web2PdfService.Companion.host
import io.github.goquati.LoggerDelegate
import io.github.goquati.LoggerDelegate.Companion.measureExecutionTime
import io.github.goquati.service.Web2PdfService
import io.github.goquati.service.Web2PdfService.Companion.host
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.http.ResponseEntity
Expand Down Expand Up @@ -38,8 +38,13 @@ class Web2PdfController(

private suspend fun Web2PdfRequestDto.toPdfResponse(): ResponseEntity<Resource> =
log.measureExecutionTime("generated PDF ($host)") {
web2PdfService.generatePdf(this)
}.let { ResponseEntity.ok<Resource>(ByteArrayResource(it)) }
web2PdfService.generatePdf(
url = url,
headers = headers,
cookies = cookies,
options = options,
)
}.let { ResponseEntity.ok(ByteArrayResource(it)) }

companion object {
private val log by LoggerDelegate()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.klahap.service
package io.github.goquati.service

import io.github.klahap.LoggerDelegate
import io.github.goquati.LoggerDelegate
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.updateAndGet
Expand Down
42 changes: 42 additions & 0 deletions src/main/kotlin/io/github/goquati/service/Html2PdfService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.github.goquati.service

import de.smart.nexus.orchestrator.oas_model.PdfPrintOptionsDto
import io.github.goquati.Web2PdfException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import org.springframework.core.env.Environment
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import java.util.UUID

@Service
class Html2PdfService(
private val web2PdfService: Web2PdfService,
private val environment: Environment,
) {
private val htmlCache = MutableStateFlow<MutableMap<UUID, String>>(mutableMapOf())
private val host
get() = if (environment.activeProfiles.contains("local"))
"host.docker.internal"
else
"localhost"

suspend fun generatePdf(
data: String,
options: PdfPrintOptionsDto?,
): ByteArray {
val id = UUID.randomUUID()!!
htmlCache.update { it[id] = data; it }
try {
return web2PdfService.generatePdf(
url = "http://$host:8080/html2pdf-data/$id",
options = options,
)
} finally {
htmlCache.update { it.remove(id); it }
}
}

fun getHtml(id: UUID) = htmlCache.value[id]
?: throw Web2PdfException("html data not found", status = HttpStatus.NOT_FOUND)
}
29 changes: 29 additions & 0 deletions src/main/kotlin/io/github/goquati/service/Markdown2PdfService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.github.goquati.service

import de.smart.nexus.orchestrator.oas_model.PdfPrintOptionsDto
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser
import org.springframework.stereotype.Service

@Service
class Markdown2PdfService(
private val html2PdfService: Html2PdfService,
) {
suspend fun generatePdf(
data: String,
options: PdfPrintOptionsDto?,
) = html2PdfService.generatePdf(
data = data.toHtml(),
options = options,
)

private final val flavour = CommonMarkFlavourDescriptor()
private final val markdownParser = MarkdownParser(flavour)

private fun String.toHtml() = HtmlGenerator(
markdownText = this,
root = markdownParser.buildMarkdownTreeFromString(this),
flavour = flavour,
).generateHtml()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.github.klahap.service
package io.github.goquati.service

import de.smart.nexus.orchestrator.oas_model.PdfPrintOptionsDto
import de.smart.nexus.orchestrator.oas_model.Web2PdfRequestDto
import io.github.klahap.Web2PdfException
import io.github.goquati.Web2PdfException
import io.ktor.http.*
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
Expand All @@ -20,22 +20,26 @@ import kotlin.io.encoding.ExperimentalEncodingApi
class Web2PdfService(
private val browserSessionService: BrowserSessionService,
) {
suspend fun generatePdf(request: Web2PdfRequestDto): ByteArray = runCatching {
val url = runCatching { Url(request.url) }.getOrElse { throw Exception("invalid url") }
suspend fun generatePdf(
url: String,
headers: Map<String, String>? = null,
cookies: Map<String, String>? = null,
options: PdfPrintOptionsDto?,
): ByteArray = runCatching {
val url = runCatching { Url(url) }.getOrElse { throw Exception("invalid url") }
browserSessionService.getPageSession().use { pageSession ->
pageSession.network.addHeaders(request.headers ?: emptyMap())
pageSession.network.addCookies(request.cookies ?: emptyMap(), host = url.host)
pageSession.network.addHeaders(headers ?: emptyMap())
pageSession.network.addCookies(cookies ?: emptyMap(), host = url.host)
runCatching {
pageSession.goto(url.toString())
}.getOrElse { throw Exception("Navigation to '${url.host}' failed. The full URL is hidden for security reasons. Please ensure the URL is correct and reachable.") }

pageSession.page.printToPDF(request.options?.pdfOptions ?: PrintToPDFRequest())
pageSession.page.printToPDF(options?.pdfOptions ?: PrintToPDFRequest())
.let { @OptIn(ExperimentalEncodingApi::class) Base64.decode(it.data) }
}
}.getOrElse { throw Web2PdfException(it.message ?: "") }



private suspend fun NetworkDomain.addHeaders(data: Map<String, String>) {
if (data.isEmpty()) return
enable { }
Expand Down
63 changes: 63 additions & 0 deletions src/main/resources/oas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@ info:
version: 1.0.0

paths:
/html2pdf:
post:
tags:
- html2pdf
operationId: convertHtml2Pdf
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Html2PdfRequest'
responses:
'200':
$ref: '#/components/responses/PDF'
'400':
$ref: '#/components/responses/ErrorResponse'
/markdown2pdf:
post:
tags:
- markdown2pdf
operationId: convertMarkdown2Pdf
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Markdown2PdfRequest'
responses:
'200':
$ref: '#/components/responses/PDF'
'400':
$ref: '#/components/responses/ErrorResponse'
/web2pdf:
post:
tags:
Expand Down Expand Up @@ -103,6 +135,37 @@ components:
my-cookie: my-cookie-value
options:
$ref: '#/components/schemas/PdfPrintOptions'
Html2PdfRequest:
type: object
required:
- data
properties:
data:
type: string
example: |
<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>Hello</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>
options:
$ref: '#/components/schemas/PdfPrintOptions'
Markdown2PdfRequest:
type: object
required:
- data
properties:
data:
type: string
example: |
# Hello World
options:
$ref: '#/components/schemas/PdfPrintOptions'
PdfPrintOptions:
type: object
properties:
Expand Down

0 comments on commit 3d5f42d

Please sign in to comment.