Giter Site home page Giter Site logo

joseluisgs / kotlin-ktor-rest-service Goto Github PK

View Code? Open in Web Editor NEW
7.0 3.0 4.0 493 KB

Servicio web para crear una API REST usando Kotlin y Kator así como otras tecnologías propuestas por JetBrains.

License: MIT License

Kotlin 98.79% HTML 1.21%
kotlin ktor api-rest

kotlin-ktor-rest-service's Introduction

Kotlin Ktor REST Service

Servicio web para API REST con Kotlin y Ktor.

Kotlin LISENCE GitHub

imagen

Acerca de

El proyecto consiste en realizar un servicio REST con Kotlin y Ktor. Para ello vamos a usar la tecnologías que nos propone Jetbrains para hacer todo el trabajo, desde la creación de la API REST, hasta la implementación de la misma, así como la serialización de objetos y/o acceso al almacenamiento de los mismos.

Para el almacenamiento de la información se ha usado una H2 Database donde la usamos gracias a la librería de Jetbrains Exposed.

Ktor

Ktor es un nuevo framework para desarrollar servicios y clientes asincrónicos. Es 100% Kotlin y se ejecuta en usando Coroutines. Admite proyectos multiplataforma, lo que significa que puede usarlo para cualquier proyecto dirigido a JVM, Android, iOS o Javascript. En este proyecto aprovecharemos Ktor para crear un servicio web para consumir una API REST. Además, aplicaremos Ktor para devolver páginas web.

ktor

Punto de Entrada

El servidor tiene su entrada y configuración en la clase Application. Esta lee la configuración en base al fichero de configuración y a partir de aquí se crea una instancia de la clase Application en base a la configuración de module().

Creando rutas

Las rutas se definen creando una función de extensión sobre Route. A su vez, usando DSL se definen las rutase en base a las petición HTTP sobre ella. Podemos responder a la petición usando call.respondText(), para texto; call.respondHTML(), para contenido HTML usando Kotlin HTML DSL; o call.respond() para devolver una respuesta en formato JSON o XML. finalmente asignamos esas rutas a la instancia de Application, es decir, dentro del método module(). Un ejemplo de ruta puede ser:

routing {
    // Entrada en la api
    get("/") {
        call.respondText("👋 Hola Kotlin REST Service con Kotlin-Ktor")
    }
}

Serializando a JSON

Para serializar objetos a JSON, usamos la librería de serialización de Kotlin, especialmente para hacer la negociación de contenido en JSON.

Para ello, las clases POJO a serailizar son indicadas con @Serializable.

import kotlinx.serialization.Serializable

@Serializable
data class Customer(var id: String, val firstName: String, val lastName: String, val email: String)

Posteriormente, en nuestra Application de Ktor, instalamos como un plugin la negociación de contenido en JSON.

install(ContentNegotiation) {
  json()
}

Podemos dejar el Json formateado, con el constructor de serialización Kotlin de Kotlin

install(ContentNegotiation) {
  // Lo ponemos bonito :)
  json(Json {
      prettyPrint = true
      isLenient = true
  })
}

Procesando Request

Dentro de un controlador de ruta, puedes obtener acceso a una solicitud utilizando la propiedad call.request. Esto devuelve la instancia de ApplicationRequest y proporciona acceso a varios parámetros de solicitud.

routing {
    get("/") {
        val uri = call.request.uri
        call.respondText("Request uri: $uri")
    }
}

Parámetros de ruta

Para obtener acceso a los valores de los parámetros de ruta mediante la propiedad call.parameters. Por ejemplo, call.parameters["login"] devolverá admin para la ruta /user/admin

get("/user/{login}") {
    if (call.parameters["login"] == "admin") {
        call.respondText("Request admin: ${call.parameters["login"]}")
    }
}

Parámetros de consulta

Para obtener acceso a los parámetros de una cadena de consulta, puede usar la propiedad ApplicationRequest.queryParameters. Por ejemplo, si se realiza una solicitud a /products?price=asc, puede acceder al parámetro de consulta de precio.

get("/products") {
    if (call.request.queryParameters["price"] == "asc") {
        call.respondText("Request price: ${call.request.queryParameters["price"]}")
    }
}

Parámetros de cuerpo

Ktor proporciona un complemento de negociación de contenido para negociar el tipo de medio de la solicitud y deserializar el contenido a un objeto de un tipo requerido. Para recibir y convertir contenido para una solicitud, llama al método de recepción que acepta una clase de datos como parámetro.

post("/customer") {
    val customer = call.receive<Customer>()
    customerStorage.add(customer)
    call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
}

Peticiones multiparte

Si necesita recibir un archivo enviado como parte de una solicitud de varias partes, llame a la función receiveMultipart y luego recorra cada parte según sea necesario. En el siguiente ejemplo, PartData.FileItem se usa para recibir un archivo como flujo de bytes.

post("/upload") {
    //  multipart data (suspending)
    val multipart = call.receiveMultipart()
    multipart.forEachPart { part ->
      val fileName = part.originalFileName as String
      var fileBytes = part.streamProvider().readBytes()
      File("uploads/$fileName").writeBytes(fileBytes)
      part.dispose()
    }
    call.respondText("$fileName is uploaded to 'uploads/$fileName'")
}

Autenticación y Autorización

Podemos implementar métodos de autenticación y autorización variados con Ktor. Este ejemplo se ha procedido a usar JWT Tokens. Para ello se ha instalado las librerías necesarias para el procesamiento de tokens JWT. Los parámetros para generar el token se han configurado en el fichero de configuración. Debemos tener en cuenta algunos parámetros para proteger y verificar los tokens, así como su tiempo de vida. Posteriormente lo instalamos como un plugin más en la configuración de la aplicación. Podemos configurar su verificador y ademas validar el payload para analizar que el cuerpo del token es válido, tal y como se indica el la documentación de Ktor.

install(Authentication) {
    jwt {
        // Configure jwt authentication
    }
}

Por otro lado, cuando nos logueamos, podemos generar el token y devolverlo al usuario, en base a los parámetros de configuración.

Para proteger ls rutas usamos la función athenticate. Cualquier ruta dentro de ella quedará protegida por la autenticación. Además si leemos en el Payload el usuario y administramos alguna política de permisos, podemos verificar que el usuario tiene permisos para acceder a la ruta.

routing {
    authenticate("auth-jwt") {
        get("/hello") {
            val principal = call.principal<JWTPrincipal>()
            val username = principal!!.payload.getClaim("username").asString()
            val expiresAt = principal.expiresAt?.time?.minus(System.currentTimeMillis())
            call.respondText("Hello, $username! Token is expired at $expiresAt ms.")
        }
    }
}

Exposed SQL

exposed

Para el almacenamiento de la información se ha usado Exposed, el cual nos ofrece dos modos de operación. Hemos usado el modelo DAO para este ejemplo. Puedes ver más información al respecto en este ejemplo. Para ello trabajamos con unas tablas en la base de datos y unas clases DAO que mapean las operaciones con objetos.

Se ha seguido un patrón CRUD basado en repositorios para la mayoría de las operaciones. Para las relaciones se han usado las clases relacionadas.

// Tabla de orders
object OrdersTable : LongIdTable() {
    //Indicamos los campos de la tabla
    val customer = reference("customer_id", CustomersTable)
    val createdAt = datetime("created_at")
}

// Clase que mapea la tabla de Order en Objetos DAO
class OrderDAO(id: EntityID<Long>) : LongEntity(id) {
    // Sobre qué tabla me estoy trabajando para hacer los Bindigs del objeto con los elementos de la tabbla/fila
    companion object : LongEntityClass<OrderDAO>(OrdersTable)
    // Indicamos que este pedido tiene una relacion con cliente. 1 Pedido pertenece a 1 Cliente (1:M). Un cliente puede tener varios pedidos.
    var customer by CustomerDAO referencedOn OrdersTable.customer
    var createdAt by OrdersTable.createdAt

    // Relación inversa donde soy referenciado. 1 Pedido tiene varios contenidos (1:M). Es opcional ponerlo, pero nos ayuda a mejorar las relaciones.
    // evitando consultas y haciendo uso de los métodos.
    val contents by OrderItemDAO referrersOn OrderItemsTable.order
}

Testing

Ktor ofrece un motor de test especial que no crea un servidor web, no se une a los sockets y no realiza ninguna solicitud HTTP real. En su lugar, se conecta directamente a los mecanismos internos y procesa una llamada de aplicación directamente. Esto da como resultado una ejecución de pruebas más rápida en comparación con la ejecución de un servidor web completo para la prueba. Además, puede configurar pruebas de extremo a extremo para probar los puntos finales del servidor utilizando el cliente HTTP de Ktor.

Para ello debemos crear nuestra aplicación testeable y luego procesar el endpoint con la petición indicada.

@Test
fun testGetCustomers() = withApplication(testEnv) {
    with(handleRequest(HttpMethod.Get, "/rest//customers?limit=2")) {
        assertEquals(HttpStatusCode.OK, response.status())
        assertTrue(response.content!!.isNotEmpty())
        assertTrue(response.content!!.contains("[email protected]"))

    }
}

Además podemos testar punto a punto, usando el cliente HTTP de Ktor.

@Test
fun testGetCustomers() = runBlocking {
    val httpResponse: HttpStatement = client.get("http://localhost:6969/rest/customers?limit=2")
    val response: String = httpResponse.receive()
    assertTrue(response.isNotEmpty())
    assertTrue(response.contains("[email protected]"))
}

Referencia API REST

Recurso Customers

Get all customers

  GET /rest/customers?limit={limit}

Get customer by id

  GET /rest/customers/{id}

Update customer by id

  PUT /rest/customers/{id}

Delete customer by id

  DELETE /rest/customers/{id}

Get orders of customer by id

  GET /rest/customers/{id}/orders

Recurso Orders

Get all orders

  GET /rest/orders?limit={limit}

Get order by id

  GET /rest/orders/{id}

Update order by id

  PUT /rest/orders/{id}

Delete order by id

  DELETE /rest/orders/{id}

Get contents by order id

  GET /rest/orders/{id}

Get contents by order id

  GET /rest/orders/{id}/contents

Get total by order id

  GET /rest/orders/{id}/total

Get customer by order id

  GET /rest/orders/{id}/customer

Subida/Bajada de archivos

Get/Download file by name

  GET /rest/uploads/{fileName}

Post/Upload file

  POST /rest/uploads/

Delete file

  DELETE /rest/uploads/{fileName}

Recursos Autenticados

Login user.

  <!-- Return a JWT Token -->
  POST /rest/auth/login

Register

  POST /rest/auth/register

Me

  <!-- Needs a JWT Token -->
  GET /rest/auth/me

Get all Users

  <!-- Needs a JWT Token and ADMIN Role -->
  GET /rest/auth/users

PostMan

postman

Puedes consumir el servicio REST con PostMan. Para ello solo debes importar la colección de ejemplo y ejecutar las mismas.

Autor

Codificado con 💖 por José Luis González Sánchez

Twitter GitHub

Contacto

Cualquier cosa que necesites házmelo saber por si puedo ayudarte 💬.

        

Licencia

Este proyecto está licenciado bajo licencia MIT, si desea saber más, visite el fichero LICENSE para su uso docente y educativo.

kotlin-ktor-rest-service's People

Contributors

joseluisgs avatar

Stargazers

Anahí Salgado avatar Daniel Ideriba avatar  avatar Daniel Rodríguez Fernández avatar David Osiris Ayala avatar Mert Akdoğan avatar Francisco Toribio Respaldo avatar

Watchers

James Cloos avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.