Skip to content

Commit 2b7d881

Browse files
authored
feat: http handler add endpoint /v1/login/. (#15483)
* refactor: improve code readability for DATABEND_COMMIT_VERSION. * feat: http handler add endpoint `/v1/login/`.
1 parent 3b0195d commit 2b7d881

File tree

10 files changed

+144
-11
lines changed

10 files changed

+144
-11
lines changed

src/query/config/src/version.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ use std::sync::LazyLock;
1717
use semver::Version;
1818

1919
pub static DATABEND_COMMIT_VERSION: LazyLock<String> = LazyLock::new(|| {
20-
let git_tag = option_env!("DATABEND_GIT_SEMVER");
20+
let semver = option_env!("DATABEND_GIT_SEMVER");
2121
let git_sha = option_env!("VERGEN_GIT_SHA");
2222
let rustc_semver = option_env!("VERGEN_RUSTC_SEMVER");
2323
let timestamp = option_env!("VERGEN_BUILD_TIMESTAMP");
2424

25-
match (git_tag, git_sha, rustc_semver, timestamp) {
25+
match (semver, git_sha, rustc_semver, timestamp) {
2626
#[cfg(not(feature = "simd"))]
27-
(Some(v1), Some(v2), Some(v3), Some(v4)) => format!("{}-{}(rust-{}-{})", v1, v2, v3, v4),
27+
(Some(semver), Some(git_sha), Some(rustc_semver), Some(timestamp)) => {
28+
format!("{semver}-{git_sha}(rust-{rustc_semver}-{timestamp})")
29+
}
2830
#[cfg(feature = "simd")]
29-
(Some(v1), Some(v2), Some(v3), Some(v4)) => {
30-
format!("{}-{}-simd(rust-{}-{})", v1, v2, v3, v4)
31+
(Some(semver), Some(git_sha), Some(rustc_semver), Some(timestamp)) => {
32+
format!("{semver}-{git_sha}-simd(rust-{rustc_semver}-{timestamp})")
3133
}
3234
_ => String::new(),
3335
}

src/query/service/src/servers/http/http_services.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use poem::listener::RustlsConfig;
2929
use poem::middleware::CatchPanic;
3030
use poem::middleware::NormalizePath;
3131
use poem::middleware::TrailingSlash;
32+
use poem::post;
3233
use poem::put;
3334
use poem::Endpoint;
3435
use poem::EndpointExt;
@@ -40,6 +41,7 @@ use crate::servers::http::middleware::HTTPSessionMiddleware;
4041
use crate::servers::http::middleware::PanicHandler;
4142
use crate::servers::http::v1::clickhouse_router;
4243
use crate::servers::http::v1::list_suggestions;
44+
use crate::servers::http::v1::login_handler;
4345
use crate::servers::http::v1::query_route;
4446
use crate::servers::http::v1::streaming_load;
4547
use crate::servers::Server;
@@ -96,6 +98,7 @@ impl HttpHandler {
9698
async fn build_router(&self, sock: SocketAddr) -> impl Endpoint {
9799
let ep_v1 = Route::new()
98100
.nest("/query", query_route())
101+
.at("/login", post(login_handler))
99102
.at("/streaming_load", put(streaming_load))
100103
.at("/upload_to_stage", put(upload_to_stage))
101104
.at("/suggested_background_tasks", get(list_suggestions));

src/query/service/src/servers/http/middleware.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ impl<E: Endpoint> Endpoint for HTTPSessionEndpoint<E> {
294294
self.ep.call(req).await
295295
}
296296
Err(err) => match err.code() {
297-
ErrorCode::AUTHENTICATE_FAILURE => {
297+
ErrorCode::AUTHENTICATE_FAILURE | ErrorCode::UNKNOWN_USER => {
298298
warn!(
299299
"http auth failure: {method} {uri}, headers={:?}, error={}",
300300
sanitize_request_headers(&headers),

src/query/service/src/servers/http/v1/http_query_handlers.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub struct QueryError {
7777
}
7878

7979
impl QueryError {
80-
pub(crate) fn from_error_code(e: &ErrorCode) -> Self {
80+
pub(crate) fn from_error_code(e: ErrorCode) -> Self {
8181
QueryError {
8282
code: e.code(),
8383
message: e.display_text(),
@@ -201,7 +201,7 @@ impl QueryResponse {
201201
stats_uri: Some(make_state_uri(&id)),
202202
final_uri: Some(make_final_uri(&id)),
203203
kill_uri: Some(make_kill_uri(&id)),
204-
error: r.state.error.as_ref().map(QueryError::from_error_code),
204+
error: r.state.error.map(QueryError::from_error_code),
205205
has_result_set: r.state.has_result_set,
206206
})
207207
.with_header(HEADER_QUERY_ID, id.clone())
@@ -382,7 +382,7 @@ pub(crate) async fn query_handler(
382382
Err(e) => {
383383
error!("http query fail to start sql, error: {:?}", e);
384384
ctx.set_fail();
385-
Ok(req.fail_to_start_sql(&e).into_response())
385+
Ok(req.fail_to_start_sql(e).into_response())
386386
}
387387
}
388388
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2021 Datafuse Labs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::collections::BTreeMap;
16+
17+
use databend_common_config::QUERY_SEMVER;
18+
use databend_common_storages_fuse::TableContext;
19+
use jwt_simple::prelude::Deserialize;
20+
use jwt_simple::prelude::Serialize;
21+
use poem::error::Result as PoemResult;
22+
use poem::web::Json;
23+
use poem::IntoResponse;
24+
25+
use crate::servers::http::v1::HttpQueryContext;
26+
use crate::servers::http::v1::QueryError;
27+
28+
#[derive(Deserialize, Clone)]
29+
struct LoginRequest {
30+
pub database: Option<String>,
31+
pub role: Option<String>,
32+
pub secondary_roles: Option<Vec<String>>,
33+
pub settings: Option<BTreeMap<String, String>>,
34+
}
35+
36+
#[derive(Serialize, Deserialize, Debug, Clone)]
37+
struct LoginResponse {
38+
pub version: String,
39+
pub error: Option<QueryError>,
40+
}
41+
42+
/// Although theses can be checked for each /v1/query for now,
43+
/// getting these error in `conn()` instead of `execute()` is more in line with what users expect.
44+
async fn check_login(
45+
ctx: &HttpQueryContext,
46+
req: &LoginRequest,
47+
) -> databend_common_exception::Result<()> {
48+
let session = &ctx.session;
49+
let table_ctx = session.create_query_context().await?;
50+
if let Some(database) = &req.database {
51+
let cat = session.get_current_catalog();
52+
let cat = table_ctx.get_catalog(&cat).await?;
53+
cat.get_database(&ctx.session.get_current_tenant(), database)
54+
.await?;
55+
}
56+
if let Some(role_name) = &req.role {
57+
session.set_current_role_checked(role_name).await?;
58+
}
59+
session
60+
.set_secondary_roles_checked(req.secondary_roles.clone())
61+
.await?;
62+
63+
if let Some(conf_settings) = &req.settings {
64+
let settings = session.get_settings();
65+
for (k, v) in conf_settings {
66+
settings.set_setting(k.to_string(), v.to_string())?;
67+
}
68+
}
69+
Ok(())
70+
}
71+
72+
/// # For SQL driver implementer:
73+
/// - It is encouraged to call `/v1/login` when establishing connection, not mandatory for now.
74+
/// - May get 404 when talk to old server, may check `/health` (no `/v1` prefix) to ensure the host:port is not wrong.
75+
///
76+
/// # TODO (need design):
77+
/// - (optional) check client version.
78+
/// - Return token for auth in the following queries from this session, to make it a real login.
79+
#[poem::handler]
80+
#[async_backtrace::framed]
81+
pub(crate) async fn login_handler(
82+
ctx: &HttpQueryContext,
83+
Json(req): Json<LoginRequest>,
84+
) -> PoemResult<impl IntoResponse> {
85+
let version = QUERY_SEMVER.to_string();
86+
let error = check_login(ctx, &req)
87+
.await
88+
.map_err(QueryError::from_error_code)
89+
.err();
90+
Ok(Json(LoginResponse { version, error }))
91+
}

src/query/service/src/servers/http/v1/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
mod http_query_handlers;
1616
pub mod json_block;
1717
mod load;
18+
mod login;
1819
mod query;
1920
mod stage;
2021
mod suggestions;
@@ -29,6 +30,7 @@ pub use http_query_handlers::QueryStats;
2930
pub(crate) use json_block::JsonBlock;
3031
pub use load::streaming_load;
3132
pub use load::LoadResponse;
33+
pub(crate) use login::login_handler;
3234
pub use query::ExecuteStateKind;
3335
pub use query::ExpiringMap;
3436
pub use query::ExpiringState;

src/query/service/src/servers/http/v1/query/http_query.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub struct HttpQueryRequest {
8383
}
8484

8585
impl HttpQueryRequest {
86-
pub(crate) fn fail_to_start_sql(&self, err: &ErrorCode) -> impl IntoResponse {
86+
pub(crate) fn fail_to_start_sql(&self, err: ErrorCode) -> impl IntoResponse {
8787
metrics_incr_http_response_errors_count(err.name(), err.code());
8888
let session = self.session.as_ref().map(|s| {
8989
let txn_state = if matches!(s.txn_state, Some(TxnState::Active)) {

src/query/service/src/servers/http/v1/query/http_query_context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::sessions::SessionManager;
2525
use crate::sessions::SessionType;
2626

2727
pub struct HttpQueryContext {
28-
session: Arc<Session>,
28+
pub session: Arc<Session>,
2929
pub query_id: String,
3030
pub node_id: String,
3131
pub deduplicate_label: Option<String>,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
>>>> drop table if exists t1;
2+
>>>> drop table if exists t2;
3+
>>>> create table t1 (a int);
4+
# auth fail
5+
{"code":"401","message":"User 'user1'@'%' does not exist."}
6+
# empty body
7+
{"code":"400","message":"parse error: EOF while parsing a value at line 1 column 0"}
8+
# db
9+
{"code":1003,"message":"Unknown database 't1'","detail":""}
10+
# db not exists
11+
{"code":1003,"message":"Unknown database 't2'","detail":""}
12+
# allow unknown key
13+
null
14+
>>>> drop table if exists t1;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
3+
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
4+
. "$CURDIR"/../../../shell_env.sh
5+
6+
stmt "drop table if exists t1;"
7+
stmt "drop table if exists t2;"
8+
stmt "create table t1 (a int);"
9+
10+
echo "# auth fail"
11+
curl -s --header 'Content-Type: application/json' --request POST '127.0.0.1:8000/v1/login/' --data-raw '{}' -u user1: | jq -c ".error"
12+
echo "# empty body"
13+
curl -s --header 'Content-Type: application/json' --request POST '127.0.0.1:8000/v1/login/' -u root: | jq -c ".error"
14+
echo "# db"
15+
curl -s --header 'Content-Type: application/json' --request POST '127.0.0.1:8000/v1/login/' --data-raw '{"database":"t1"}' -u root: | jq -c ".error"
16+
echo "# db not exists"
17+
curl -s --header 'Content-Type: application/json' --request POST '127.0.0.1:8000/v1/login/' --data-raw '{"database":"t2"}' -u root: | jq -c ".error"
18+
echo "# allow unknown key"
19+
curl -s --header 'Content-Type: application/json' --request POST '127.0.0.1:8000/v1/login/' --data-raw '{"unknown": null}' -u root:xx | jq -c ".error"
20+
21+
stmt "drop table if exists t1;"

0 commit comments

Comments
 (0)