Search code examples
rustactix-web

Actix Web is not handling post request?


So I'm trying to create a basic actix-web application that will allow me to create a very basic blog system. It is handling my GET requests, but it's not handling my POST requests.

main.rs:

use actix_web::{HttpServer, App, web};
use sqlx::postgres::PgPool;
use dotenv::dotenv;

mod posts;
mod database;

#[actix_web::main]
pub async fn main() -> std::io::Result<()>{
    dotenv().ok();

    let pool = PgPool::connect(&dotenv::var("DATABASE_URL").unwrap()).await.unwrap();

    HttpServer::new(move || {
        // The scope for all post services
        let posts = web::scope("/posts").service(posts::get_all_posts).service(posts::create_post);

        App::new()
            .data(pool.clone())
            .service(web::scope("/api").service(posts))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

posts/routes.rs:

use super::*;
use database::{NewPost, model::Post};
use actix_web::{get, post, Responder, HttpResponse};

#[get("/")]
pub async fn get_all_posts(pool: web::Data<PgPool>) -> impl Responder {
    println!("New GET request for all posts!");
    let result = Post::find_all(pool.get_ref()).await;

    match result {
        Ok(posts) => HttpResponse::Ok().json(posts),
        _ => HttpResponse::BadRequest().body("Error trying to read all the posts from the database") 
    }
}

#[post("/new")]
pub async fn create_post(post: web::Json<NewPost>, pool: web::Data<PgPool>) -> impl Responder {
    println!("New POST request to create a post!");
    let result = Post::new_post(post.into_inner(), pool.as_ref()).await;

    match result {
        Ok(post) => HttpResponse::Ok().json(post),
        _ => HttpResponse::BadRequest().body("Error trying to create a new post")
    }
}

database/model.rs:

use serde::{Serialize, Deserialize};
use sqlx::{FromRow, PgPool, Row, postgres::PgRow};
use uuid::Uuid;
use chrono::prelude::*;

/// Struct to represent database record.
#[derive(Serialize, FromRow, Debug)]
pub struct Post {
    pub id: Uuid,
    pub content: String,
    pub created_at: chrono::NaiveDateTime
}

/// Struct to receive user input.
#[derive(Serialize, Deserialize)]
pub struct NewPost {
    pub content: String
}

impl Post {
    pub async fn find_all(pool: &PgPool) -> std::io::Result<Vec<Post>> {
        let mut posts = Vec::new();
        let recs = sqlx::query_as!(Post, r#"SELECT id, content, created_at FROM post ORDER BY id"#)
            .fetch_all(pool)
            .await
            .unwrap();

        for rec in recs {
            posts.push(Post {
                id: rec.id,
                content: rec.content,
                created_at: rec.created_at
            });
        }

        Ok(posts)
    }

    pub async fn new_post(post: NewPost, pool: &PgPool) -> std::io::Result<Post> {
        let mut tx = pool.begin().await.unwrap();
        let post = sqlx::query("INSERT INTO post (id, content, created_at) VALUES ($1, $2, $3) RETURNING id, content, created_at")
            .bind(Uuid::new_v4())
            .bind(post.content)
            .bind(Utc::now())
            .map(|row: PgRow| {
                Post {
                    id: row.get(0),
                    content: row.get(1),
                    created_at: row.get(2)
                }
            })
            .fetch_one(&mut tx)
            .await
            .unwrap();

        tx.commit().await.unwrap();
        Ok(post)
    }
}

Currently, I am using curl to test out the API. When I want to send a GET request I use the command:

curl localhost:8080/api/posts/

When I run this command my rust application prints out the statement: "New GET request for all posts!" as I expected it to.

When I want to send a POST request I use the command:

curl -H "Content-Type: application/json" -X POST -d '{ \
    "content": "This is my first post" \
}' localhost:8080/api/posts/new

After running this curl command I don't get any output at all, there is nothing from the curl terminal nor any output from my rust program.


Solution

  • Just reading over everything, your curl might actually be the culprit.

    If you include -i to show the response headers, then you can see you're actually receiving a 400 Bad Request.

    $ curl -i -H "Content-Type: application/json" -X POST -d '{ \
    >     "content": "This is my first post" \
    > }' localhost:8080/api/posts/new
    HTTP/1.1 400 Bad Request
    content-length: 0
    date: Sun, 03 Jan 2021 14:04:12 GMT
    

    If you change create_post to take post: String instead of post: web::Json<NewPost>, then it makes it easier to inspect the payload the endpoint is receiving.

    pub async fn create_post(post: String) -> impl Responder {
        println!("{}", post);
    

    Now performing the same curl reveals the following output:

    { \
        "content": "This is my first post" \
    }
    

    In short, the issue is that the backslashes remain in the payload. So the only thing you need to do to get it working, is to remove the backslashes.

    curl -H "Content-Type: application/json" -X POST -d '{
        "content": "This is my first post"
    }' localhost:8080/api/posts/new