Rust API в докере с базой данных Postgresql, ошибка запроса POST

Я пытаюсь создать простой CRUD API на Rust, следуя этому руководству, и сейчас нахожусь на этапе тестирования своих усилий. К сожалению, я столкнулся с ошибкой при использовании запроса POST, который, по сути, является общим INTERNAL_SERVER_ERROR, который я написал.

Я тестирую API через Postman, с методом, установленным на POST, URL-адресом, установленным на localhost:8080/logs, и телом, установленным на необработанный JSON со следующим содержимым:

{
    "app_id": "ROBA",
    "log_title": "Meaningful log title",
    "app_title": "Robot Battles",
    "log_content": "Error at this line with this module, etc."
}

Вот мой docker-compose.yml:

версия: '3.9'

services:
  rust_db:
    container_name: rust_db
    image: postgres
    environment:
      POSTGRES_DB: apploggerstorage
      POSTGRES_USER: apploggeruser
      POSTGRES_PASSWORD: apploggerpw1234
    ports:
      - '5432:5432'
    volumes:
      - pgdata:/var/lib/postgresql/data
  rust_app_logger:
    container_name: rust_app_logger
    image: lanceguinto/rust_app_logger:1.0.0
    build:
      context: .
      dockerfile: Dockerfile
      args:
        DATABASE_URL: postgres://apploggeruser:apploggerpw1234@rust_db:5432/apploggerstorage
    ports:
      - '8080:8080'
    depends_on:
      - rust_db

volumes:
  pgdata: {}

А вот сокращенная версия моего main.rs:

use postgres::{Client, NoTls};
use postgres::Error as PostgresError;
use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::env;
use chrono::prelude::*;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
struct AppLog {
    id: Option<i32>, // Option because ID is not provided by app but by database
    app_id: String,
    log_title: String, 
    app_title: String,
    log_content: String,
    log_datetime: String,
}

const DB_URL: &str = env!("DATABASE_URL");

const OK_RESPONSE: &str = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n";
const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
const INTERNAL_SERVER_ERROR: &str = "HTTP/1.1 500 INTERNAL SERVER ERROR\r\n\r\n";

fn main() {
    if let Err(e) = set_database() {
        print!("Error: {}", e);
        return;
    }

    let listener = TcpListener::bind(format!("0.0.0.0:8080")).unwrap();
    println!("Server started at port 8080.");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => {
                println!("Error: {}", e);
            }
        }
    }
}

fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    let mut request = String::new();

    match stream.read(&mut buffer) {
        Ok(size) => {
            request.push_str(String::from_utf8_lossy(&buffer[..size]).as_ref());
            let (status_line, content) = match &*request {
                r if r.starts_with("POST /logs") => handle_post_request(r),
                _ => (NOT_FOUND.to_string(), "404 Not Found".to_string()),
            };

            stream.write_all(format!("{}{}", status_line, content).as_bytes()).unwrap();
        }
        Err(e) => {
            println!("Error: {}", e);
        }
    }
}

fn handle_post_request(request: &str) -> (String, String) {
    match (get_applog_request_body(&request), Client::connect(DB_URL, NoTls)) {
        (Ok(app_logger), Ok(mut client)) => {
            let utc: DateTime<Utc> = Utc::now();
            client.execute(
            "INSERT INTO logs (appId, logTitle, appTitle, logContent, logDateTime) VALUES ($1, $2, $3, $4, $5)", 
            &[&app_logger.app_id, &app_logger.log_title, &app_logger.app_title, &app_logger.log_content, &utc.to_string()]
            ).unwrap();

            (OK_RESPONSE.to_string(), "Log posted".to_string())
        }

        _ => {
            // This is what shows up
            println!("Error: {}", INTERNAL_SERVER_ERROR);
            (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string())
        }
    }
}

fn set_database() -> Result<(), PostgresError> {
    let client = Client::connect(DB_URL, NoTls);

    client?.batch_execute(
        "CREATE TABLE IF NOT EXISTS logs (
            id SERIAL PRIMARY KEY, 
            appId TEXT NOT NULL, 
            logTitle TEXT NOT NULL, 
            appTitle TEXT, 
            logContent TEXT, 
            logDateTime TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
        )"
    )?;
    Ok(())
}

fn get_applog_request_body(request: &str) -> Result<AppLog, serde_json::Error> {
    serde_json::from_str(request.split("\r\n\r\n").last().unwrap_or_default())
}

И наконец, мой Dockerfile:

FROM rust:1.69-buster as builder
WORKDIR /app
# Build Args
ARG DATABASE_URL
ENV DATABASE_URL=$DATABASE_URL
COPY . .
RUN cargo build --release
FROM debian:buster-slim
WORKDIR /usr/local/bin
COPY --from=builder /app/target/release/app-logger .
CMD ["./app-logger"]

Попытки добавить в базу данных через POST просто приводят к ошибке, и пока я не совсем уверен, что делаю не так. Любая помощь приветствуется.

rust_app_logger    | Get App Log Request Body: POST /logs HTTP/1.1
rust_app_logger    | Content-Type: application/json
rust_app_logger    | User-Agent: PostmanRuntime/7.28.2
rust_app_logger    | Accept: */*
rust_app_logger    | Cache-Control: no-cache
rust_app_logger    | Postman-Token: f148f831-3bf5-4541-8924-75ae005639ad
rust_app_logger    | Host: localhost:8080
rust_app_logger    | Accept-Encoding: gzip, deflate, br
rust_app_logger    | Connection: keep-alive
rust_app_logger    | Content-Length: 168
rust_app_logger    |
rust_app_logger    | {
rust_app_logger    |     "app_id": "ROBA",
rust_app_logger    |     "log_title": "Meaningful log title",
rust_app_logger    |     "app_title": "Robot Battles",
rust_app_logger    |     "log_content": "Error at this line with this module, etc."
rust_app_logger    | }
rust_app_logger    | Error: HTTP/1.1 500 INTERNAL SERVER ERROR

моя первая мысль — сеть докеров, попробуйте использовать expose вместо ports в вашем docker-compose.yaml

GreenChicken 04.04.2024 13:50

@Jmb, повторно добавил ошибку в виде текста.

zack_falcon 08.04.2024 06:49

Попробуйте распечатать ошибки, а не отбрасывать их. Например. вместо сопоставления _ => { … } попробуйте что-то вроде: (a, c) => { if let Err (e) = a { println!("Error: app_logger: {e:?}"); } if let Err (e) = c { println!("Error: client: {e:?}"); } … }

Jmb 08.04.2024 08:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как предложил @Jmb выше, мне пришлось написать больше кода, чтобы лучше понять проблему, поэтому я добавил следующее в match case в fn handle_post_request:

(Ok(_app_logger), Err(e)) => {
    println!("Error with the client connection: {}", e);
    (INTERNAL_SERVER_ERROR.to_string(), e.to_string())
}

(Err(e), Ok(_client)) => {
    println!("Error with getting applog request body: {}", e);
    (INTERNAL_SERVER_ERROR.to_string(),  e.to_string())
}

Это подсказало мне суть проблемы; их было несколько, но в первую очередь это терпит неудачу сразу же при попытке получить тело запроса и попытаться превратить его в AppLog, потому что структура AppLog ожидает log_datetime, чего я не делал (и не должен) передать как часть тела запроса.

Поэтому я изменил структуру AppLog на следующую:

struct AppLog {
    id: Option<i32>, // Option because ID is not provided by app but by database
    app_id: String,
    log_title: String, 
    app_title: String,
    log_content: String,
    log_datetime: Option<DateTime<Utc>>, // Option because log date time is not provided by request but by API / database
}

Далее последовал ряд вопросов:

  1. Что-то о том, что DateTime не поддерживается Deserialize или Serialize; Гораздо позже я понял, что мне также нужно изменить файл Cargo.toml, чтобы включить serde в файл Chronic. Исходил из этого:
chrono = 0.4

К этому:

chrono = {version = "0.4", features = ["serde"]}
  1. В моем API также есть запросы GET, но у них были проблемы с получением журналов из БД и выводом их в виде массива структуры AppLog. Гораздо позже выяснилось, что мне также нужно изменить зависимость postgres в Cargo.toml, исходя из этого:
postgres = 0.4

К этому:

postgres = {version = "0.19.2", features = ["with-chrono-0_4"]}
  1. Третья проблема заключается в том, что оператор INSERT по-прежнему не работает при вставке utc.to_string(). Я все еще работаю над этим, но на данный момент я удалил этот параметр и поле из инструкции INSERT в качестве обходного пути, поскольку таблица все равно вставляет now() по умолчанию.

Другие вопросы по теме