mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/Bnet: Rewrite LoginRESTService using boost::beast instead of gsoap as http backend and extract generic http code to be reusable elsewhere
This commit is contained in:
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
httpget.c
|
||||
|
||||
gSOAP HTTP GET plugin.
|
||||
|
||||
See instructions below.
|
||||
|
||||
gSOAP XML Web services tools
|
||||
Copyright (C) 2000-2008, Robert van Engelen, Genivia Inc., All Rights Reserved.
|
||||
This part of the software is released under ONE of the following licenses:
|
||||
GPL or the gSOAP public license.
|
||||
--------------------------------------------------------------------------------
|
||||
gSOAP public license.
|
||||
|
||||
The contents of this file are subject to the gSOAP Public License Version 1.3
|
||||
(the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
http://www.cs.fsu.edu/~engelen/soaplicense.html
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the License.
|
||||
|
||||
The Initial Developer of the Original Code is Robert A. van Engelen.
|
||||
Copyright (C) 2000-2008 Robert A. van Engelen, Genivia inc. All Rights Reserved.
|
||||
--------------------------------------------------------------------------------
|
||||
GPL license.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Author contact information:
|
||||
engelen@genivia.com / engelen@acm.org
|
||||
|
||||
This program is released under the GPL with the additional exemption that
|
||||
compiling, linking, and/or using OpenSSL is allowed.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Compile & link with stand-alone gSOAP server for HTTP GET support.
|
||||
Compile & link with gSOAP clients for client-side HTTP GET requests.
|
||||
|
||||
Usage (server side):
|
||||
|
||||
struct soap soap;
|
||||
soap_init(&soap);
|
||||
soap_register_plugin_arg(&soap, http_get, http_get_handler);
|
||||
...
|
||||
... = soap_copy(&soap); // copies plugin too but not its data: plugin data is shared since fcopy is not set
|
||||
...
|
||||
soap_done(&soap); // detach plugin (calls plugin->fdelete)
|
||||
|
||||
You need to define a HTTP GET handling function at the server-side:
|
||||
|
||||
int http_get_handler(struct soap*)
|
||||
|
||||
which will be called from the plugin upon an HTTP GET request.
|
||||
The function should return an error code or SOAP_OK;
|
||||
This function should produce a valid HTTP response, for example:
|
||||
|
||||
soap_response(soap, SOAP_HTML); // use this to return HTML ...
|
||||
soap_response(soap, SOAP_OK); // ... or use this to return a SOAP message
|
||||
...
|
||||
soap_send(soap, "<HTML>...</HTML>"); // example HTML
|
||||
...
|
||||
soap_end_send(soap);
|
||||
|
||||
To get query string key-value pairs within a service routine, use:
|
||||
|
||||
char *s;
|
||||
s = soap_query(soap);
|
||||
while (s)
|
||||
{
|
||||
char *key = soap_query_key(soap, &s);
|
||||
char *val = soap_query_val(soap, &s);
|
||||
...
|
||||
}
|
||||
|
||||
This will garble soap->path, which contains the HTTP path of the form:
|
||||
/path?query
|
||||
where path and/or ?query may be absent. The path info is obtained from
|
||||
the HTTP request URL: http://domain/path?query
|
||||
The URL should not exceed the length of SOAP_TAGLEN. Adjust SOAP_TAGLEN
|
||||
in stdsoap2.h if necessary.
|
||||
|
||||
Usage (client side):
|
||||
|
||||
For SOAP GET method calls, declare the service method with protocol GET:
|
||||
|
||||
//gsoap ns service method-protocol: someMethod GET
|
||||
int ns__someMethod(... in-params ..., struct ns__someMethodResponse { ... out-params ... } *);
|
||||
|
||||
and to make the call in your code:
|
||||
|
||||
struct ns__someMethodResponse res;
|
||||
soap_call_ns__someMethod(soap, "endpoint", "action", ... in-params ...., &res)
|
||||
|
||||
Client code for non-SOAP REST GET:
|
||||
|
||||
To use general HTTP GET, for example to retrieve an HTML document, use:
|
||||
|
||||
struct soap soap;
|
||||
char *buf = NULL;
|
||||
size_t len = 0;
|
||||
soap_init(&soap);
|
||||
if (soap_GET(&soap, endpoint, action)
|
||||
|| soap_begin_recv(&soap))
|
||||
... connect/recv error ...
|
||||
else
|
||||
buf = soap_http_get_body(&soap, &len);
|
||||
soap_end_recv(&soap);
|
||||
... process data in buf[0..len-1]
|
||||
soap_destroy(&soap);
|
||||
soap_end(&soap);
|
||||
soap_done(&soap);
|
||||
|
||||
The soap_http_get_body() function above returns the HTTP body content
|
||||
as a string.
|
||||
|
||||
*/
|
||||
|
||||
#include "httpget.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const char http_get_id[] = HTTP_GET_ID;
|
||||
|
||||
static int http_get_init(struct soap *soap, struct http_get_data *data, int (*handler)(struct soap*));
|
||||
static void http_get_delete(struct soap *soap, struct soap_plugin *p);
|
||||
static int http_get_parse(struct soap *soap);
|
||||
static int http_get_handler(struct soap *soap);
|
||||
|
||||
int http_get(struct soap *soap, struct soap_plugin *p, void *arg)
|
||||
{
|
||||
p->id = http_get_id;
|
||||
p->data = (void*)SOAP_MALLOC(soap, sizeof(struct http_get_data));
|
||||
/* p->fcopy = http_get_copy; obsolete, see note with http_get_copy() */
|
||||
p->fdelete = http_get_delete;
|
||||
if (!p->data)
|
||||
return SOAP_EOM;
|
||||
if (http_get_init(soap, (struct http_get_data*)p->data, (int (*)(struct soap*))arg))
|
||||
{
|
||||
SOAP_FREE(soap, p->data); /* error: could not init */
|
||||
return SOAP_EOM; /* return error */
|
||||
}
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
static int http_get_init(struct soap *soap, struct http_get_data *data, int (*handler)(struct soap*))
|
||||
{
|
||||
data->fparse = soap->fparse; /* save old HTTP header parser callback */
|
||||
data->fget = handler;
|
||||
data->stat_get = 0;
|
||||
data->stat_post = 0;
|
||||
data->stat_fail = 0;
|
||||
memset((void*)data->hist_min, 0, sizeof(data->hist_min));
|
||||
memset((void*)data->hist_hour, 0, sizeof(data->hist_hour));
|
||||
memset((void*)data->hist_day, 0, sizeof(data->hist_day));
|
||||
soap->fparse = http_get_parse; /* replace HTTP header parser callback with ours */
|
||||
soap->fget = http_get_handler; /* replace HTTP GET callback with ours */
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
/* We will share the plugin data among all soap copies created with soap_copy(), so we don't have to define this
|
||||
static int http_get_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src)
|
||||
{
|
||||
*dst = *src;
|
||||
return SOAP_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
static void http_get_delete(struct soap *soap, struct soap_plugin *p)
|
||||
{
|
||||
(void)soap;
|
||||
SOAP_FREE(soap, p->data); /* free allocated plugin data (this function is not called for shared plugin data, but only when the final soap_done() is invoked on the original soap struct) */
|
||||
}
|
||||
|
||||
static int http_get_parse(struct soap *soap)
|
||||
{
|
||||
#ifndef WITH_LEAN
|
||||
time_t t;
|
||||
#ifdef HAVE_LOCALTIME_R
|
||||
struct tm T;
|
||||
#endif
|
||||
struct tm *pT;
|
||||
#endif
|
||||
struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id);
|
||||
if (!data)
|
||||
return SOAP_PLUGIN_ERROR;
|
||||
#ifndef WITH_LEAN
|
||||
time(&t);
|
||||
#ifdef HAVE_LOCALTIME_R
|
||||
pT = localtime_r(&t, &T);
|
||||
#else
|
||||
pT = localtime(&t);
|
||||
#endif
|
||||
/* updates should be in mutex, but we don't mind some inaccuracy in the count to preserve performance */
|
||||
data->hist_day[pT->tm_yday]++;
|
||||
data->hist_day[(pT->tm_yday + 1) % 365] = 0;
|
||||
data->hist_hour[pT->tm_hour]++;
|
||||
data->hist_hour[(pT->tm_hour + 1) % 24] = 0;
|
||||
data->hist_min[pT->tm_min]++;
|
||||
data->hist_min[(pT->tm_min + 1) % 60] = 0;
|
||||
#endif
|
||||
soap->error = data->fparse(soap); /* parse HTTP header */
|
||||
if (!soap->error)
|
||||
{
|
||||
if (soap->status == SOAP_POST)
|
||||
data->stat_post++; /* update should be in mutex, but we don't mind some inaccuracy in the count */
|
||||
}
|
||||
else
|
||||
{
|
||||
data->stat_fail++; /* update should be in mutex, but we don't mind some inaccuracy in the count */
|
||||
}
|
||||
return soap->error;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static int http_get_handler(struct soap *soap)
|
||||
{
|
||||
struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id);
|
||||
if (!data)
|
||||
return SOAP_PLUGIN_ERROR;
|
||||
soap->error = data->fget(soap); /* call user-defined HTTP GET handler */
|
||||
if (soap->error)
|
||||
{
|
||||
/* update should be in mutex, but we don't mind some inaccuracy in the count */
|
||||
data->stat_fail++;
|
||||
return soap->error;
|
||||
}
|
||||
/* update should be in mutex, but we don't mind some inaccuracy in the count */
|
||||
data->stat_get++;
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
void soap_http_get_stats(struct soap *soap, size_t *stat_get, size_t *stat_post, size_t *stat_fail, size_t **hist_min, size_t **hist_hour, size_t **hist_day)
|
||||
{
|
||||
struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id);
|
||||
if (!data)
|
||||
return;
|
||||
if (stat_get)
|
||||
*stat_get = data->stat_get;
|
||||
if (stat_post)
|
||||
*stat_post = data->stat_post;
|
||||
if (stat_fail)
|
||||
*stat_fail = data->stat_fail;
|
||||
if (hist_min)
|
||||
*hist_min = data->hist_min;
|
||||
if (hist_hour)
|
||||
*hist_hour = data->hist_hour;
|
||||
if (hist_day)
|
||||
*hist_day = data->hist_day;
|
||||
}
|
||||
|
||||
/* deprecated: use soap_GET instead */
|
||||
int soap_http_get_connect(struct soap *soap, const char *endpoint, const char *action)
|
||||
{
|
||||
return soap_GET(soap, endpoint, action);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
httpget.h
|
||||
|
||||
gSOAP HTTP GET plugin.
|
||||
|
||||
See httpget.c for usage instructions.
|
||||
|
||||
gSOAP XML Web services tools
|
||||
Copyright (C) 2000-2008, Robert van Engelen, Genivia Inc., All Rights Reserved.
|
||||
This part of the software is released under ONE of the following licenses:
|
||||
GPL or the gSOAP public license.
|
||||
--------------------------------------------------------------------------------
|
||||
gSOAP public license.
|
||||
|
||||
The contents of this file are subject to the gSOAP Public License Version 1.3
|
||||
(the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
http://www.cs.fsu.edu/~engelen/soaplicense.html
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the License.
|
||||
|
||||
The Initial Developer of the Original Code is Robert A. van Engelen.
|
||||
Copyright (C) 2000-2008 Robert A. van Engelen, Genivia inc. All Rights Reserved.
|
||||
--------------------------------------------------------------------------------
|
||||
GPL license.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Author contact information:
|
||||
engelen@genivia.com / engelen@acm.org
|
||||
|
||||
This program is released under the GPL with the additional exemption that
|
||||
compiling, linking, and/or using OpenSSL is allowed.
|
||||
--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef HTTPGET_H
|
||||
#define HTTPGET_H
|
||||
|
||||
#include "stdsoap2.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HTTP_GET_ID "SOAP-HTTP-GET/2.1" /* plugin identification */
|
||||
|
||||
extern const char http_get_id[];
|
||||
|
||||
/* This is the local plugin data shared among all copies of the soap struct: */
|
||||
struct http_get_data
|
||||
{
|
||||
int (*fparse)(struct soap*); /* to save and call the internal HTTP header parser */
|
||||
int (*fget)(struct soap*); /* user-defined server-side HTTP GET handler */
|
||||
size_t stat_get; /* HTTP GET usage statistics */
|
||||
size_t stat_post; /* HTTP POST usage statistics */
|
||||
size_t stat_fail; /* HTTP failure statistics */
|
||||
size_t hist_min[60]; /* Hits by the minute */
|
||||
size_t hist_hour[24]; /* Hits by the hour */
|
||||
size_t hist_day[366]; /* Hits by day */
|
||||
};
|
||||
|
||||
int http_get(struct soap*, struct soap_plugin*, void*);
|
||||
int soap_http_get_connect(struct soap*, const char*, const char*);
|
||||
|
||||
void soap_http_get_stats(struct soap *soap, size_t *stat_get, size_t *stat_post, size_t *stat_fail, size_t **hist_min, size_t **hist_hour, size_t **hist_day);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,368 +0,0 @@
|
||||
/*
|
||||
httppost.c
|
||||
|
||||
gSOAP HTTP POST/PUT/DELETE plugin for SOAP and non-SOAP payloads.
|
||||
|
||||
See instructions below.
|
||||
|
||||
Revisions:
|
||||
register multiple POST content handlers, each for a content type
|
||||
|
||||
Note: multipart/related and multipart/form-data are already handled in gSOAP.
|
||||
|
||||
gSOAP XML Web services tools
|
||||
Copyright (C) 2000-2018, Robert van Engelen, Genivia, Inc. All Rights Reserved.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
gSOAP public license.
|
||||
|
||||
The contents of this file are subject to the gSOAP Public License Version 1.3
|
||||
(the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
http://www.cs.fsu.edu/~engelen/soaplicense.html
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the License.
|
||||
|
||||
The Initial Developer of the Original Code is Robert A. van Engelen.
|
||||
Copyright (C) 2000-2018 Robert A. van Engelen, Genivia inc. All Rights Reserved.
|
||||
--------------------------------------------------------------------------------
|
||||
GPL license.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Author contact information:
|
||||
engelen@genivia.com / engelen@acm.org
|
||||
|
||||
This program is released under the GPL with the additional exemption that
|
||||
compiling, linking, and/or using OpenSSL is allowed.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Compile & link with stand-alone gSOAP server.
|
||||
|
||||
Usage (server side):
|
||||
|
||||
Define a NULL-terminated array of type-handler pairs, each with a media
|
||||
type and a the handler function:
|
||||
|
||||
struct http_post_handlers my_handlers[] = {
|
||||
{ "application/json", json_post_handler },
|
||||
{ "image/jpg", jpeg_post_handler },
|
||||
{ "text/html", html_post_handler },
|
||||
{ "POST", generic_POST_handler },
|
||||
{ "PUT", generic_PUT_handler },
|
||||
{ "PATCH", generic_PATCH_handler },
|
||||
{ "DELETE", generic_DELETE_handler },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
A `generic_POST_handler`, when specified with a `"POST"` key entry in
|
||||
the table, takes priority over `soap_serve()` if no `SOAPAction` HTTP
|
||||
header is included in the message. This means that SOAP/XML messages
|
||||
without a `SOAPAction` header will not be processed by `soap_serve()`!
|
||||
|
||||
Note that `*` and `-` can be used as wildcards to match any text and
|
||||
any character, respectively:
|
||||
|
||||
{ "image*", image_post_handler },
|
||||
{ "text*", text_post_handler },
|
||||
{ "text/---", three_char_type_text_handler },
|
||||
|
||||
In the above, to be more accurate, we should use a slash / between
|
||||
image and the wildcard * (which is not shown in the table above due to
|
||||
compilers throwing a fit at the / and * combo in this comment block).
|
||||
|
||||
It is possible to specify other paramters that must match:
|
||||
|
||||
{ "text/html; charset=utf-8", html_post_handler },
|
||||
|
||||
Media types may have optional parameters after `;` such as `charset`
|
||||
and `boundary`. These parameters can be matched by the media type
|
||||
patterns in the table. Patterns that are more specific must precede
|
||||
patterns that are less specific in the table. For example,
|
||||
`"text/xml;*charset=utf-8*"` must precede `"text/xml"` which must
|
||||
precede `"text*"`. Note that `"text/xml"` also matches any parameters
|
||||
of the media type of the message reveived, such as `"text/xml;
|
||||
charset=utf-8"` (only since gSOAP version 2.8.75).
|
||||
|
||||
Each handler is a function that takes the soap context as a parameter
|
||||
and returns SOAP_OK or an error code.
|
||||
|
||||
Register the plugin and the handlers:
|
||||
|
||||
struct soap soap;
|
||||
soap_init(&soap);
|
||||
soap_register_plugin_arg(&soap, http_post, my_handlers);
|
||||
...
|
||||
... = soap_copy(&soap); // copies plugin too but not its data: plugin data is shared since fcopy is not set
|
||||
...
|
||||
soap_done(&soap); // delete plugin (calls plugin->fdelete)
|
||||
|
||||
A POST handler function is triggered by the media type in the
|
||||
http_post_handlers table. Use http_http_get_body() as below to retrieve
|
||||
HTTP POST body data:
|
||||
|
||||
int image_post_handler(struct soap *soap)
|
||||
{
|
||||
const char *buf;
|
||||
size_t len;
|
||||
// if necessary, check type in soap->http_content
|
||||
if (soap->http_content && soap_tag_cmp(soap->http_content, "image/gif"))
|
||||
return 404;
|
||||
buf = soap_http_get_body(soap, &len);
|
||||
if (!buf)
|
||||
return soap->error;
|
||||
soap_end_recv(soap);
|
||||
... // process image in buf[0..len-1]
|
||||
// reply with empty HTTP OK response:
|
||||
return soap_send_empty_response(soap, SOAP_OK);
|
||||
}
|
||||
|
||||
This function should also produce a valid HTTP response, for example:
|
||||
|
||||
if (we want to return HTML)
|
||||
{
|
||||
if (soap_response(soap, SOAP_HTML) // use this to return HTML...
|
||||
|| soap_send(soap, "<HTML>...</HTML>"); // example HTML
|
||||
|| ...
|
||||
|| soap_end_send(soap))
|
||||
return soap_closesock(soap);
|
||||
return SOAP_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
soap->http_content = "image/jpeg"; // a jpeg image
|
||||
if (soap_response(soap, SOAP_FILE) // SOAP_FILE sets custom http content
|
||||
|| soap_send_raw(soap, ..., ...);
|
||||
|| ...
|
||||
|| soap_end_send(soap))
|
||||
return soap_closesock(soap);
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
The soap_closesock() call only closes the connection when it shoud not
|
||||
be kept alive. This function is called automatically after the handler
|
||||
returns. There is no harm in calling this function more than once.
|
||||
|
||||
The soap_send(soap, char*) and soap_send_raw(soap, char*, size_t) can
|
||||
be used to return content from server.
|
||||
|
||||
The soap_response(soap, SOAP_FILE) call returns a HTTP 200 OK response
|
||||
with the HTTP content-type header set to soap->http_content. To return
|
||||
a http error status code use soap_response(soap, SOAP_FILE + status)
|
||||
with status between 200 and 599 or 0.
|
||||
|
||||
Usage (client side):
|
||||
|
||||
char *buf; // to hold the HTTP response body data
|
||||
size_t len;
|
||||
...
|
||||
if (soap_post_connect(soap, "URL", "SOAP action or NULL", "media type")
|
||||
|| soap_send(soap, ...)
|
||||
|| soap_end_send(soap))
|
||||
... // error
|
||||
if (soap_begin_recv(&soap)
|
||||
|| soap_http_body(&soap, &buf, &len)
|
||||
|| soap_end_recv(&soap))
|
||||
... // error
|
||||
... // use buf[0..len-1]
|
||||
soap_closesock(soap); // close, but keep open only with HTTP keep-alive
|
||||
soap_end(soap); // also deletes buf content
|
||||
|
||||
The soap_send(soap, char*) and soap_send_raw(soap, char*, size_t) can
|
||||
be used to send content to the server as shown above.
|
||||
|
||||
*/
|
||||
|
||||
#include "httppost.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const char http_post_id[] = HTTP_POST_ID;
|
||||
|
||||
static int http_post_init(struct soap *soap, struct http_post_data *data, struct http_post_handlers *handlers);
|
||||
static void http_post_delete(struct soap *soap, struct soap_plugin *p);
|
||||
static int http_post_parse_header(struct soap *soap, const char*, const char*);
|
||||
static http_handler_t http_lookup_handler(struct soap *soap, const char *type, struct http_post_data *data);
|
||||
|
||||
static int http_fput(struct soap *soap);
|
||||
static int http_fpatch(struct soap *soap);
|
||||
static int http_fdel(struct soap *soap);
|
||||
|
||||
int http_post(struct soap *soap, struct soap_plugin *p, void *arg)
|
||||
{
|
||||
p->id = http_post_id;
|
||||
p->data = (void*)SOAP_MALLOC(soap, sizeof(struct http_post_data));
|
||||
p->fdelete = http_post_delete;
|
||||
if (!p->data)
|
||||
return SOAP_EOM;
|
||||
if (http_post_init(soap, (struct http_post_data*)p->data, (struct http_post_handlers *)arg))
|
||||
{
|
||||
SOAP_FREE(soap, p->data); /* error: could not init */
|
||||
return SOAP_EOM; /* return error */
|
||||
}
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
static int http_post_init(struct soap *soap, struct http_post_data *data, struct http_post_handlers *handlers)
|
||||
{
|
||||
data->fparsehdr = soap->fparsehdr; /* save old HTTP header parser callback */
|
||||
soap->fparsehdr = http_post_parse_header; /* replace HTTP header parser callback with ours */
|
||||
data->fput = soap->fput;
|
||||
soap->fput = http_fput;
|
||||
data->fpatch = soap->fpatch;
|
||||
soap->fpatch = http_fpatch;
|
||||
data->fdel = soap->fdel;
|
||||
soap->fdel = http_fdel;
|
||||
data->handlers = handlers;
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
static void http_post_delete(struct soap *soap, struct soap_plugin *p)
|
||||
{
|
||||
soap->fparsehdr = ((struct http_post_data*)p->data)->fparsehdr;
|
||||
soap->fput = ((struct http_post_data*)p->data)->fput;
|
||||
soap->fpatch = ((struct http_post_data*)p->data)->fpatch;
|
||||
soap->fdel = ((struct http_post_data*)p->data)->fdel;
|
||||
SOAP_FREE(soap, p->data); /* free allocated plugin data (this function is not called for shared plugin data, but only when the final soap_done() is invoked on the original soap struct) */
|
||||
}
|
||||
|
||||
static int http_post_parse_header(struct soap *soap, const char *key, const char *val)
|
||||
{
|
||||
struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
|
||||
if (!data)
|
||||
return SOAP_PLUGIN_ERROR;
|
||||
if (data->fparsehdr(soap, key, val)) /* parse HTTP header */
|
||||
return soap->error;
|
||||
if (!soap_tag_cmp(key, "Content-Type")) /* check content type */
|
||||
soap->fform = http_lookup_handler(soap, val, data);
|
||||
if (!soap->fform)
|
||||
soap->fform = http_lookup_handler(soap, "POST", data);
|
||||
return soap->error;
|
||||
}
|
||||
|
||||
static http_handler_t http_lookup_handler(struct soap *soap, const char *type, struct http_post_data *data)
|
||||
{
|
||||
struct http_post_handlers *p;
|
||||
const char *params = strchr(type, ';');
|
||||
char temp[SOAP_HDRLEN];
|
||||
(void)soap;
|
||||
if (params)
|
||||
{
|
||||
size_t n = params - type;
|
||||
soap_strncpy(temp, sizeof(temp), type, n);
|
||||
temp[n] = '\0';
|
||||
}
|
||||
for (p = data->handlers; p && p->type; p++)
|
||||
{
|
||||
if (params)
|
||||
{
|
||||
if (strchr(p->type, ';'))
|
||||
{
|
||||
if (!soap_tag_cmp(type, p->type))
|
||||
break;
|
||||
}
|
||||
else if (!soap_tag_cmp(temp, p->type))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!soap_tag_cmp(type, p->type))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p && p->type)
|
||||
{
|
||||
DBGLOG(TEST,SOAP_MESSAGE(fdebug, "Found HTTP POST plugin handler for '%s'\n", type));
|
||||
return p->handler;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int http_fput(struct soap *soap)
|
||||
{
|
||||
int (*fform)(struct soap*);
|
||||
struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
|
||||
if (!data)
|
||||
return SOAP_PLUGIN_ERROR;
|
||||
fform = http_lookup_handler(soap, "PUT", data);
|
||||
if (fform)
|
||||
return fform(soap);
|
||||
return data->fput(soap);
|
||||
}
|
||||
|
||||
static int http_fpatch(struct soap *soap)
|
||||
{
|
||||
int (*fform)(struct soap*);
|
||||
struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
|
||||
if (!data)
|
||||
return SOAP_PLUGIN_ERROR;
|
||||
fform = http_lookup_handler(soap, "PATCH", data);
|
||||
if (fform)
|
||||
return fform(soap);
|
||||
return data->fpatch(soap);
|
||||
}
|
||||
|
||||
static int http_fdel(struct soap *soap)
|
||||
{
|
||||
int (*fform)(struct soap*);
|
||||
struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
|
||||
if (!data)
|
||||
return SOAP_PLUGIN_ERROR;
|
||||
fform = http_lookup_handler(soap, "DELETE", data);
|
||||
if (fform)
|
||||
return fform(soap);
|
||||
return data->fdel(soap);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* call soap_http_get_body to receive HTTP body and set buf and len */
|
||||
int soap_http_body(struct soap *soap, char **buf, size_t *len)
|
||||
{
|
||||
*buf = soap_http_get_body(soap, len);
|
||||
if (*buf)
|
||||
return SOAP_OK;
|
||||
return soap->error;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* deprecated: use soap_POST instead */
|
||||
int soap_post_connect(struct soap *soap, const char *endpoint, const char *action, const char *type)
|
||||
{
|
||||
return soap_POST(soap, endpoint, action, type);
|
||||
}
|
||||
|
||||
/* deprecated: use soap_PUT instead */
|
||||
int soap_put_connect(struct soap *soap, const char *endpoint, const char *action, const char *type)
|
||||
{
|
||||
return soap_PUT(soap, endpoint, action, type);
|
||||
}
|
||||
|
||||
/* deprecated: use soap_DELETE instead */
|
||||
int soap_delete_connect(struct soap *soap, const char *endpoint)
|
||||
{
|
||||
return soap_DELETE(soap, endpoint);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
httppost.h
|
||||
|
||||
gSOAP HTTP POST plugin for non-SOAP payloads.
|
||||
|
||||
See httppost.c for instructions.
|
||||
|
||||
Revisions:
|
||||
register multiple POST content handlers, each for a content type
|
||||
|
||||
gSOAP XML Web services tools
|
||||
Copyright (C) 2000-2018, Robert van Engelen, Genivia, Inc. All Rights Reserved.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
gSOAP public license.
|
||||
|
||||
The contents of this file are subject to the gSOAP Public License Version 1.3
|
||||
(the "License"); you may not use this file except in compliance with the
|
||||
License. You may obtain a copy of the License at
|
||||
http://www.cs.fsu.edu/~engelen/soaplicense.html
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the License.
|
||||
|
||||
The Initial Developer of the Original Code is Robert A. van Engelen.
|
||||
Copyright (C) 2000-2004 Robert A. van Engelen, Genivia inc. All Rights Reserved.
|
||||
--------------------------------------------------------------------------------
|
||||
GPL license.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Author contact information:
|
||||
engelen@genivia.com / engelen@acm.org
|
||||
|
||||
This program is released under the GPL with the additional exemption that
|
||||
compiling, linking, and/or using OpenSSL is allowed.
|
||||
--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef HTTPPOST_H
|
||||
#define HTTPPOST_H
|
||||
|
||||
#include "stdsoap2.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HTTP_POST_ID "SOAP-HTTP-POST/2.2" /* plugin identification */
|
||||
|
||||
extern const char http_post_id[];
|
||||
|
||||
typedef int (*http_handler_t)(struct soap*);
|
||||
|
||||
struct http_post_handlers
|
||||
{
|
||||
const char *type;
|
||||
http_handler_t handler;
|
||||
};
|
||||
|
||||
/* This is the local plugin data shared among all copies of the soap struct: */
|
||||
struct http_post_data
|
||||
{
|
||||
int (*fparsehdr)(struct soap*, const char*, const char*); /* to save and call the internal HTTP header parser */
|
||||
int (*fput)(struct soap*); /* to save */
|
||||
int (*fpatch)(struct soap*); /* to save */
|
||||
int (*fdel)(struct soap*); /* to save */
|
||||
struct http_post_handlers *handlers; /* the server-side POST content type handlers */
|
||||
};
|
||||
|
||||
/* the http post plugin, note: argument should be a table of type-handler pairs */
|
||||
int http_post(struct soap*, struct soap_plugin*, void*);
|
||||
|
||||
/* deprecated: use soap_POST instead */
|
||||
int soap_post_connect(struct soap*, const char *endpoint, const char *action, const char *type);
|
||||
|
||||
/* deprecated: use soap_PUT instead */
|
||||
int soap_put_connect(struct soap*, const char *endpoint, const char *action, const char *type);
|
||||
|
||||
/* deprecated: use soap_DELETE instead */
|
||||
int soap_delete_connect(struct soap*, const char *endpoint);
|
||||
|
||||
/* deprecated: use soap_get_http_body instead */
|
||||
int soap_http_body(struct soap*, char **buf, size_t *len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -24,12 +24,7 @@
|
||||
using SHA1 = Trinity::Crypto::SHA1;
|
||||
using SRP6 = Trinity::Crypto::SRP6;
|
||||
|
||||
/*static*/ std::array<uint8, 1> const SRP6::g = []()
|
||||
{
|
||||
std::array<uint8, 1> g_temp;
|
||||
g_temp[0] = 7;
|
||||
return g_temp;
|
||||
}();
|
||||
/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
|
||||
/*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true);
|
||||
/*static*/ BigNumber const SRP6::_g(SRP6::g);
|
||||
/*static*/ BigNumber const SRP6::_N(N);
|
||||
|
||||
33
src/common/Utilities/Concepts.h
Normal file
33
src/common/Utilities/Concepts.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_CONCEPTS_H
|
||||
#define TRINITYCORE_CONCEPTS_H
|
||||
|
||||
#include <concepts>
|
||||
#include <functional> // std::invoke
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
template <typename Callable, typename R, typename... Args>
|
||||
concept invocable_r = requires(Callable && callable, Args&&... args)
|
||||
{
|
||||
{ std::invoke(static_cast<Callable&&>(callable), static_cast<Args&&>(args)...) } -> std::convertible_to<R>;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_CONCEPTS_H
|
||||
@@ -200,21 +200,29 @@ int main(int argc, char** argv)
|
||||
|
||||
Trinity::Net::ScanLocalNetworks();
|
||||
|
||||
// Start the listening port (acceptor) for auth connections
|
||||
int32 bnport = sConfigMgr->GetIntDefault("BattlenetPort", 1119);
|
||||
if (bnport < 0 || bnport > 0xFFFF)
|
||||
std::string httpBindIp = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
|
||||
int32 httpPort = sConfigMgr->GetIntDefault("LoginREST.Port", 8081);
|
||||
if (httpPort <= 0 || httpPort > 0xFFFF)
|
||||
{
|
||||
TC_LOG_ERROR("server.bnetserver", "Specified battle.net port ({}) out of allowed range (1-65535)", bnport);
|
||||
TC_LOG_ERROR("server.bnetserver", "Specified login service port ({}) out of allowed range (1-65535)", httpPort);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!sLoginService.Start(ioContext.get()))
|
||||
if (!sLoginService.StartNetwork(*ioContext, httpBindIp, httpPort))
|
||||
{
|
||||
TC_LOG_ERROR("server.bnetserver", "Failed to initialize login service");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::shared_ptr<void> sLoginServiceHandle(nullptr, [](void*) { sLoginService.Stop(); });
|
||||
std::shared_ptr<void> sLoginServiceHandle(nullptr, [](void*) { sLoginService.StopNetwork(); });
|
||||
|
||||
// Start the listening port (acceptor) for auth connections
|
||||
int32 bnport = sConfigMgr->GetIntDefault("BattlenetPort", 1119);
|
||||
if (bnport <= 0 || bnport > 0xFFFF)
|
||||
{
|
||||
TC_LOG_ERROR("server.bnetserver", "Specified battle.net port ({}) out of allowed range (1-65535)", bnport);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the list of realms for the server
|
||||
sRealmList->Initialize(*ioContext, sConfigMgr->GetIntDefault("RealmsStateUpdateDelay", 10));
|
||||
|
||||
118
src/server/bnetserver/REST/LoginHttpSession.cpp
Normal file
118
src/server/bnetserver/REST/LoginHttpSession.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "LoginHttpSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "LoginRESTService.h"
|
||||
#include "SslContext.h"
|
||||
#include "Util.h"
|
||||
|
||||
namespace Battlenet
|
||||
{
|
||||
LoginHttpSession::LoginHttpSession(boost::asio::ip::tcp::socket&& socket)
|
||||
: SslSocket(std::move(socket), SslContext::instance())
|
||||
{
|
||||
}
|
||||
|
||||
LoginHttpSession::~LoginHttpSession() = default;
|
||||
|
||||
void LoginHttpSession::Start()
|
||||
{
|
||||
std::string ip_address = GetRemoteIpAddress().to_string();
|
||||
TC_LOG_TRACE("server.http.session", "{} Accepted connection", GetClientInfo());
|
||||
|
||||
// Verify that this IP is not in the ip_banned table
|
||||
LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
|
||||
stmt->setString(0, ip_address);
|
||||
|
||||
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithPreparedCallback([sess = shared_from_this()](PreparedQueryResult result) { sess->CheckIpCallback(std::move(result)); }));
|
||||
}
|
||||
|
||||
void LoginHttpSession::CheckIpCallback(PreparedQueryResult result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
bool banned = false;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
if (fields[0].GetUInt64() != 0)
|
||||
banned = true;
|
||||
|
||||
} while (result->NextRow());
|
||||
|
||||
if (banned)
|
||||
{
|
||||
TC_LOG_DEBUG("server.http.session", "{} tries to log in using banned IP!", GetClientInfo());
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncHandshake();
|
||||
}
|
||||
|
||||
Trinity::Net::Http::RequestHandlerResult LoginHttpSession::RequestHandler(Trinity::Net::Http::RequestContext& context)
|
||||
{
|
||||
return sLoginService.HandleRequest(shared_from_this(), context);
|
||||
}
|
||||
|
||||
std::shared_ptr<Trinity::Net::Http::SessionState> LoginHttpSession::ObtainSessionState(Trinity::Net::Http::RequestContext& context) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
std::shared_ptr<Trinity::Net::Http::SessionState> state;
|
||||
|
||||
auto cookieItr = context.request.find(boost::beast::http::field::cookie);
|
||||
if (cookieItr != context.request.end())
|
||||
{
|
||||
std::vector<std::string_view> cookies = Trinity::Tokenize(Trinity::Net::Http::ToStdStringView(cookieItr->value()), ';', false);
|
||||
std::size_t eq = 0;
|
||||
auto sessionIdItr = std::find_if(cookies.begin(), cookies.end(), [&](std::string_view cookie)
|
||||
{
|
||||
std::string_view name = cookie;
|
||||
eq = cookie.find('=');
|
||||
if (eq != std::string_view::npos)
|
||||
name = cookie.substr(0, eq);
|
||||
|
||||
return name == SESSION_ID_COOKIE;
|
||||
});
|
||||
if (sessionIdItr != cookies.end())
|
||||
{
|
||||
std::string_view value = sessionIdItr->substr(eq + 1);
|
||||
state = sLoginService.FindAndRefreshSessionState(value, GetRemoteIpAddress());
|
||||
}
|
||||
}
|
||||
|
||||
if (!state)
|
||||
{
|
||||
state = sLoginService.CreateNewSessionState(GetRemoteIpAddress());
|
||||
|
||||
std::string_view host = Trinity::Net::Http::ToStdStringView(context.request[boost::beast::http::field::host]);
|
||||
if (std::size_t port = host.find(':'); port != std::string_view::npos)
|
||||
host.remove_suffix(host.length() - port);
|
||||
|
||||
context.response.insert(boost::beast::http::field::set_cookie, Trinity::StringFormat("{}={}; Path=/bnetserver; Domain={}; Secure; HttpOnly; SameSite=None",
|
||||
SESSION_ID_COOKIE, boost::uuids::to_string(state->Id), host));
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
43
src/server/bnetserver/REST/LoginHttpSession.h
Normal file
43
src/server/bnetserver/REST/LoginHttpSession.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_LOGIN_HTTP_SESSION_H
|
||||
#define TRINITYCORE_LOGIN_HTTP_SESSION_H
|
||||
|
||||
#include "HttpSslSocket.h"
|
||||
|
||||
namespace Battlenet
|
||||
{
|
||||
class LoginHttpSession : public Trinity::Net::Http::SslSocket<LoginHttpSession>
|
||||
{
|
||||
public:
|
||||
static constexpr std::string_view SESSION_ID_COOKIE = "JSESSIONID";
|
||||
|
||||
explicit LoginHttpSession(boost::asio::ip::tcp::socket&& socket);
|
||||
~LoginHttpSession();
|
||||
|
||||
void Start() override;
|
||||
|
||||
void CheckIpCallback(PreparedQueryResult result);
|
||||
|
||||
Trinity::Net::Http::RequestHandlerResult RequestHandler(Trinity::Net::Http::RequestContext& context) override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Trinity::Net::Http::SessionState> ObtainSessionState(Trinity::Net::Http::RequestContext& context) const override;
|
||||
};
|
||||
}
|
||||
#endif // TRINITYCORE_LOGIN_HTTP_SESSION_H
|
||||
@@ -16,71 +16,63 @@
|
||||
*/
|
||||
|
||||
#include "LoginRESTService.h"
|
||||
#include "Base64.h"
|
||||
#include "Configuration/Config.h"
|
||||
#include "CryptoHash.h"
|
||||
#include "CryptoRandom.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Errors.h"
|
||||
#include "IpNetwork.h"
|
||||
#include "IteratorPair.h"
|
||||
#include "ProtobufJSON.h"
|
||||
#include "Resolver.h"
|
||||
#include "SslContext.h"
|
||||
#include "Util.h"
|
||||
#include "httpget.h"
|
||||
#include "httppost.h"
|
||||
#include "soapH.h"
|
||||
#include <boost/uuid/string_generator.hpp>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int ns1__executeCommand(soap*, char*, char**) { return SOAP_OK; }
|
||||
|
||||
class AsyncRequest
|
||||
namespace Battlenet
|
||||
{
|
||||
public:
|
||||
AsyncRequest(soap const& server) : _client(server), _responseStatus(0) { }
|
||||
|
||||
AsyncRequest(AsyncRequest const&) = delete;
|
||||
AsyncRequest& operator=(AsyncRequest const&) = delete;
|
||||
AsyncRequest(AsyncRequest&&) = default;
|
||||
AsyncRequest& operator=(AsyncRequest&&) = default;
|
||||
|
||||
bool InvokeIfReady()
|
||||
{
|
||||
ASSERT(_callback);
|
||||
return _callback->InvokeIfReady();
|
||||
}
|
||||
|
||||
soap* GetClient() { return &_client; }
|
||||
void SetCallback(std::unique_ptr<QueryCallback> callback) { _callback = std::move(callback); }
|
||||
int32 GetResponseStatus() const { return _responseStatus; }
|
||||
void SetResponseStatus(int32 responseStatus) { _responseStatus = responseStatus; }
|
||||
|
||||
private:
|
||||
soap _client;
|
||||
std::unique_ptr<QueryCallback> _callback;
|
||||
int32 _responseStatus;
|
||||
};
|
||||
|
||||
int32 handle_get_plugin(soap* soapClient)
|
||||
LoginRESTService& LoginRESTService::Instance()
|
||||
{
|
||||
return sLoginService.HandleHttpRequest(soapClient, "GET", sLoginService._getHandlers);
|
||||
static LoginRESTService instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
int32 handle_post_plugin(soap* soapClient)
|
||||
bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount)
|
||||
{
|
||||
return sLoginService.HandleHttpRequest(soapClient, "POST", sLoginService._postHandlers);
|
||||
}
|
||||
if (!HttpService::StartNetwork(ioContext, bindIp, port, threadCount))
|
||||
return false;
|
||||
|
||||
bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext)
|
||||
{
|
||||
_ioContext = ioContext;
|
||||
_bindIP = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
|
||||
_port = sConfigMgr->GetIntDefault("LoginREST.Port", 8081);
|
||||
if (_port < 0 || _port > 0xFFFF)
|
||||
using Trinity::Net::Http::RequestHandlerFlag;
|
||||
|
||||
RegisterHandler(boost::beast::http::verb::get, "/bnetserver/login/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
TC_LOG_ERROR("server.rest", "Specified login service port ({}) out of allowed range (1-65535), defaulting to 8081", _port);
|
||||
_port = 8081;
|
||||
}
|
||||
return HandleGetForm(std::move(session), context);
|
||||
});
|
||||
|
||||
Trinity::Asio::Resolver resolver(*ioContext);
|
||||
RegisterHandler(boost::beast::http::verb::get, "/bnetserver/gameAccounts/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
return HandleGetGameAccounts(std::move(session), context);
|
||||
});
|
||||
|
||||
RegisterHandler(boost::beast::http::verb::get, "/bnetserver/portal/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
return HandleGetPortal(std::move(session), context);
|
||||
});
|
||||
|
||||
RegisterHandler(boost::beast::http::verb::post, "/bnetserver/login/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
return HandlePostLogin(std::move(session), context);
|
||||
}, RequestHandlerFlag::DoNotLogRequestContent);
|
||||
|
||||
RegisterHandler(boost::beast::http::verb::post, "/bnetserver/refreshLoginTicket/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
return HandlePostRefreshLoginTicket(std::move(session), context);
|
||||
});
|
||||
|
||||
_bindIP = bindIp;
|
||||
_port = port;
|
||||
|
||||
Trinity::Asio::Resolver resolver(ioContext);
|
||||
|
||||
_hostnames[0] = sConfigMgr->GetStringDefault("LoginREST.ExternalAddress", "127.0.0.1");
|
||||
Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), _hostnames[0], std::to_string(_port));
|
||||
@@ -103,8 +95,8 @@ bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext)
|
||||
_addresses[1] = localAddress->address();
|
||||
|
||||
// set up form inputs
|
||||
Battlenet::JSON::Login::FormInput* input;
|
||||
_formInputs.set_type(Battlenet::JSON::Login::LOGIN_FORM);
|
||||
JSON::Login::FormInput* input;
|
||||
_formInputs.set_type(JSON::Login::LOGIN_FORM);
|
||||
input = _formInputs.add_inputs();
|
||||
input->set_input_id("account_name");
|
||||
input->set_type("text");
|
||||
@@ -124,16 +116,10 @@ bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext)
|
||||
|
||||
_loginTicketDuration = sConfigMgr->GetIntDefault("LoginREST.TicketDuration", 3600);
|
||||
|
||||
_thread = std::thread(&LoginRESTService::Run, this);
|
||||
_acceptor->AsyncAcceptWithCallback<&LoginRESTService::OnSocketAccept>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoginRESTService::Stop()
|
||||
{
|
||||
_stopped = true;
|
||||
_thread.join();
|
||||
}
|
||||
|
||||
std::string const& LoginRESTService::GetHostnameForClient(boost::asio::ip::address const& address) const
|
||||
{
|
||||
if (auto addressIndex = Trinity::Net::SelectAddressForClient(address, _addresses))
|
||||
@@ -145,117 +131,53 @@ std::string const& LoginRESTService::GetHostnameForClient(boost::asio::ip::addre
|
||||
return _hostnames[0];
|
||||
}
|
||||
|
||||
void LoginRESTService::Run()
|
||||
std::string LoginRESTService::ExtractAuthorization(HttpRequest const& request)
|
||||
{
|
||||
soap soapServer(SOAP_C_UTFSTRING, SOAP_C_UTFSTRING);
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
// check every 3 seconds if world ended
|
||||
soapServer.accept_timeout = 3;
|
||||
soapServer.recv_timeout = 5;
|
||||
soapServer.send_timeout = 5;
|
||||
if (!soap_valid_socket(soap_bind(&soapServer, _bindIP.c_str(), _port, 100)))
|
||||
{
|
||||
TC_LOG_ERROR("server.rest", "Couldn't bind to {}:{}", _bindIP, _port);
|
||||
return;
|
||||
}
|
||||
std::string ticket;
|
||||
auto itr = request.find(boost::beast::http::field::authorization);
|
||||
if (itr == request.end())
|
||||
return ticket;
|
||||
|
||||
TC_LOG_INFO("server.rest", "Login service bound to http://{}:{}", _bindIP, _port);
|
||||
std::string_view authorization = Trinity::Net::Http::ToStdStringView(itr->value());
|
||||
constexpr std::string_view BASIC_PREFIX = "Basic "sv;
|
||||
|
||||
http_post_handlers handlers[] =
|
||||
{
|
||||
{ "application/json;charset=utf-8", handle_post_plugin },
|
||||
{ "application/json", handle_post_plugin },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
if (authorization.starts_with(BASIC_PREFIX))
|
||||
authorization.remove_prefix(BASIC_PREFIX.length());
|
||||
|
||||
_getHandlers["/bnetserver/login/"] = &LoginRESTService::HandleGetForm;
|
||||
_getHandlers["/bnetserver/gameAccounts/"] = &LoginRESTService::HandleGetGameAccounts;
|
||||
_getHandlers["/bnetserver/portal/"] = &LoginRESTService::HandleGetPortal;
|
||||
Optional<std::vector<uint8>> decoded = Trinity::Encoding::Base64::Decode(authorization);
|
||||
if (!decoded)
|
||||
return ticket;
|
||||
|
||||
_postHandlers["/bnetserver/login/"] = &LoginRESTService::HandlePostLogin;
|
||||
_postHandlers["/bnetserver/refreshLoginTicket/"] = &LoginRESTService::HandlePostRefreshLoginTicket;
|
||||
std::string_view decodedHeader(reinterpret_cast<char const*>(decoded->data()), decoded->size());
|
||||
|
||||
soap_register_plugin_arg(&soapServer, &http_get, (void*)&handle_get_plugin);
|
||||
soap_register_plugin_arg(&soapServer, &http_post, handlers);
|
||||
soap_register_plugin_arg(&soapServer, &ContentTypePlugin::Init, (void*)"application/json;charset=utf-8");
|
||||
soap_register_plugin_arg(&soapServer, &ResponseCodePlugin::Init, nullptr);
|
||||
if (std::size_t ticketEnd = decodedHeader.find(':'); ticketEnd != std::string_view::npos)
|
||||
decodedHeader.remove_suffix(decodedHeader.length() - ticketEnd);
|
||||
|
||||
// Use our already ready ssl context
|
||||
soapServer.ctx = Battlenet::SslContext::instance().native_handle();
|
||||
soapServer.ssl_flags = SOAP_SSL_RSA;
|
||||
|
||||
while (!_stopped)
|
||||
{
|
||||
if (!soap_valid_socket(soap_accept(&soapServer)))
|
||||
continue; // ran into an accept timeout
|
||||
|
||||
std::shared_ptr<AsyncRequest> soapClient = std::make_shared<AsyncRequest>(soapServer);
|
||||
if (soap_ssl_accept(soapClient->GetClient()) != SOAP_OK)
|
||||
{
|
||||
TC_LOG_DEBUG("server.rest", "Failed SSL handshake from IP={}", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("server.rest", "Accepted connection from IP={}", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string());
|
||||
|
||||
Trinity::Asio::post(*_ioContext, [soapClient]()
|
||||
{
|
||||
soapClient->GetClient()->user = (void*)&soapClient; // this allows us to make a copy of pointer inside GET/POST handlers to increment reference count
|
||||
soap_begin(soapClient->GetClient());
|
||||
soap_begin_recv(soapClient->GetClient());
|
||||
});
|
||||
}
|
||||
|
||||
// and release the context handle here - soap does not own it so it should not free it on exit
|
||||
soapServer.ctx = nullptr;
|
||||
|
||||
TC_LOG_INFO("server.rest", "Login service exiting...");
|
||||
ticket = decodedHeader;
|
||||
return ticket;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers)
|
||||
LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetForm(std::shared_ptr<LoginHttpSession> /*session*/, HttpRequestContext& context)
|
||||
{
|
||||
TC_LOG_DEBUG("server.rest", "[{}:{}] Handling {} request path=\"{}\"",
|
||||
boost::asio::ip::address_v4(soapClient->ip).to_string(), soapClient->port, method, soapClient->path);
|
||||
|
||||
size_t pathLength = strlen(soapClient->path);
|
||||
if (char const* queryPart = strchr(soapClient->path, '?'))
|
||||
pathLength = queryPart - soapClient->path;
|
||||
|
||||
auto handler = handlers.find(std::string{ soapClient->path, pathLength });
|
||||
if (handler != handlers.end())
|
||||
{
|
||||
int32 status = (this->*handler->second)(*reinterpret_cast<std::shared_ptr<AsyncRequest>*>(soapClient->user));
|
||||
if (status != SOAP_OK)
|
||||
{
|
||||
ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = status;
|
||||
return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse());
|
||||
}
|
||||
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = 404;
|
||||
return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse());
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(_formInputs);
|
||||
return RequestHandlerResult::Handled;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::HandleGetForm(std::shared_ptr<AsyncRequest> request)
|
||||
LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
return SendResponse(request->GetClient(), _formInputs);
|
||||
}
|
||||
std::string ticket = ExtractAuthorization(context.request);
|
||||
if (ticket.empty())
|
||||
return HandleUnauthorized(std::move(session), context);
|
||||
|
||||
int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request)
|
||||
{
|
||||
if (!request->GetClient()->userid)
|
||||
return 401;
|
||||
|
||||
request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] {
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST);
|
||||
stmt->setString(0, request->GetClient()->userid);
|
||||
return stmt;
|
||||
}())
|
||||
.WithPreparedCallback([this, request](PreparedQueryResult result)
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST);
|
||||
stmt->setString(0, ticket);
|
||||
session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithPreparedCallback([session, context = std::move(context)](PreparedQueryResult result) mutable
|
||||
{
|
||||
Battlenet::JSON::Login::GameAccountList response;
|
||||
JSON::Login::GameAccountList gameAccounts;
|
||||
if (result)
|
||||
{
|
||||
auto formatDisplayName = [](char const* name) -> std::string
|
||||
@@ -270,7 +192,7 @@ int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> requ
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
Battlenet::JSON::Login::GameAccountInfo* gameAccount = response.add_game_accounts();
|
||||
JSON::Login::GameAccountInfo* gameAccount = gameAccounts.add_game_accounts();
|
||||
gameAccount->set_display_name(formatDisplayName(fields[0].GetCString()));
|
||||
gameAccount->set_expansion(fields[1].GetUInt8());
|
||||
if (!fields[2].IsNull())
|
||||
@@ -285,40 +207,37 @@ int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> requ
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
SendResponse(request->GetClient(), response);
|
||||
})));
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(gameAccounts);
|
||||
session->SendResponse(context);
|
||||
}));
|
||||
|
||||
Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
|
||||
|
||||
return SOAP_OK;
|
||||
return RequestHandlerResult::Async;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::HandleGetPortal(std::shared_ptr<AsyncRequest> request)
|
||||
LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
std::string const& hostname = GetHostnameForClient(boost::asio::ip::address_v4(request->GetClient()->ip));
|
||||
std::string response = Trinity::StringFormat("{}:{}", hostname, sConfigMgr->GetIntDefault("BattlenetPort", 1119));
|
||||
|
||||
soap_response(request->GetClient(), SOAP_FILE);
|
||||
soap_send_raw(request->GetClient(), response.c_str(), response.length());
|
||||
return soap_end_send(request->GetClient());
|
||||
context.response.set(boost::beast::http::field::content_type, "text/plain");
|
||||
context.response.body() = Trinity::StringFormat("{}:{}", GetHostnameForClient(session->GetRemoteIpAddress()), sConfigMgr->GetIntDefault("BattlenetPort", 1119));
|
||||
return RequestHandlerResult::Handled;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
|
||||
LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
char* buf = nullptr;
|
||||
size_t len = 0;
|
||||
soap_http_body(request->GetClient(), &buf, &len);
|
||||
|
||||
Battlenet::JSON::Login::LoginForm loginForm;
|
||||
if (!buf || !JSON::Deserialize(buf, &loginForm))
|
||||
JSON::Login::LoginForm loginForm;
|
||||
if (!::JSON::Deserialize(context.request.body(), &loginForm))
|
||||
{
|
||||
ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = 400;
|
||||
|
||||
Battlenet::JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(Battlenet::JSON::Login::LOGIN);
|
||||
JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(JSON::Login::LOGIN);
|
||||
loginResult.set_error_code("UNABLE_TO_DECODE");
|
||||
loginResult.set_error_message("There was an internal error while connecting to Battle.net. Please try again later.");
|
||||
return SendResponse(request->GetClient(), loginResult);
|
||||
|
||||
context.response.result(boost::beast::http::status::bad_request);
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(loginResult);
|
||||
session->SendResponse(context);
|
||||
|
||||
return RequestHandlerResult::Handled;
|
||||
}
|
||||
|
||||
std::string login;
|
||||
@@ -340,44 +259,32 @@ int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
|
||||
|
||||
std::string sentPasswordHash = CalculateShaPassHash(login, password);
|
||||
|
||||
request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithChainingPreparedCallback([request, login, sentPasswordHash, this](QueryCallback& callback, PreparedQueryResult result)
|
||||
session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithChainingPreparedCallback([this, session, context = std::move(context), login = std::move(login), sentPasswordHash = std::move(sentPasswordHash)](QueryCallback& callback, PreparedQueryResult result) mutable
|
||||
{
|
||||
if (result)
|
||||
if (!result)
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 accountId = fields[0].GetUInt32();
|
||||
std::string pass_hash = fields[1].GetString();
|
||||
uint32 failedLogins = fields[2].GetUInt32();
|
||||
std::string loginTicket = fields[3].GetString();
|
||||
uint32 loginTicketExpiry = fields[4].GetUInt32();
|
||||
bool isBanned = fields[5].GetUInt64() != 0;
|
||||
JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(JSON::Login::DONE);
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(loginResult);
|
||||
session->SendResponse(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sentPasswordHash == pass_hash)
|
||||
Field* fields = result->Fetch();
|
||||
uint32 accountId = fields[0].GetUInt32();
|
||||
std::string pass_hash = fields[1].GetString();
|
||||
uint32 failedLogins = fields[2].GetUInt32();
|
||||
std::string loginTicket = fields[3].GetString();
|
||||
uint32 loginTicketExpiry = fields[4].GetUInt32();
|
||||
bool isBanned = fields[5].GetUInt64() != 0;
|
||||
|
||||
if (sentPasswordHash != pass_hash)
|
||||
{
|
||||
if (!isBanned)
|
||||
{
|
||||
if (loginTicket.empty() || loginTicketExpiry < time(nullptr))
|
||||
{
|
||||
std::array<uint8, 20> ticket = Trinity::Crypto::GetRandomBytes<20>();
|
||||
|
||||
loginTicket = "TC-" + ByteArrayToHexStr(ticket);
|
||||
}
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION);
|
||||
stmt->setString(0, loginTicket);
|
||||
stmt->setUInt32(1, time(nullptr) + _loginTicketDuration);
|
||||
stmt->setUInt32(2, accountId);
|
||||
callback.WithPreparedCallback([request, loginTicket](PreparedQueryResult)
|
||||
{
|
||||
Battlenet::JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(Battlenet::JSON::Login::DONE);
|
||||
loginResult.set_login_ticket(loginTicket);
|
||||
sLoginService.SendResponse(request->GetClient(), loginResult);
|
||||
}).SetNextQuery(LoginDatabase.AsyncQuery(stmt));
|
||||
return;
|
||||
}
|
||||
else if (!isBanned)
|
||||
{
|
||||
std::string ip_address = boost::asio::ip::address_v4(request->GetClient()->ip).to_string();
|
||||
std::string ip_address = session->GetRemoteIpAddress().to_string();
|
||||
uint32 maxWrongPassword = uint32(sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0));
|
||||
|
||||
if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false))
|
||||
@@ -421,31 +328,50 @@ int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
|
||||
LoginDatabase.CommitTransaction(trans);
|
||||
}
|
||||
}
|
||||
|
||||
JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(JSON::Login::DONE);
|
||||
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(loginResult);
|
||||
session->SendResponse(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Battlenet::JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(Battlenet::JSON::Login::DONE);
|
||||
sLoginService.SendResponse(request->GetClient(), loginResult);
|
||||
})));
|
||||
if (loginTicket.empty() || loginTicketExpiry < time(nullptr))
|
||||
loginTicket = "TC-" + ByteArrayToHexStr(Trinity::Crypto::GetRandomBytes<20>());
|
||||
|
||||
Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION);
|
||||
stmt->setString(0, loginTicket);
|
||||
stmt->setUInt32(1, time(nullptr) + _loginTicketDuration);
|
||||
stmt->setUInt32(2, accountId);
|
||||
callback.WithPreparedCallback([session, context = std::move(context), loginTicket = std::move(loginTicket)](PreparedQueryResult) mutable
|
||||
{
|
||||
JSON::Login::LoginResult loginResult;
|
||||
loginResult.set_authentication_state(JSON::Login::DONE);
|
||||
loginResult.set_login_ticket(loginTicket);
|
||||
|
||||
return SOAP_OK;
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(loginResult);
|
||||
session->SendResponse(context);
|
||||
}).SetNextQuery(LoginDatabase.AsyncQuery(stmt));
|
||||
}));
|
||||
|
||||
return RequestHandlerResult::Async;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request)
|
||||
LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
|
||||
{
|
||||
if (!request->GetClient()->userid)
|
||||
return 401;
|
||||
std::string ticket = ExtractAuthorization(context.request);
|
||||
if (ticket.empty())
|
||||
return HandleUnauthorized(std::move(session), context);
|
||||
|
||||
request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] {
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION);
|
||||
stmt->setString(0, request->GetClient()->userid);
|
||||
return stmt;
|
||||
}())
|
||||
.WithPreparedCallback([this, request](PreparedQueryResult result)
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION);
|
||||
stmt->setString(0, ticket);
|
||||
session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
|
||||
.WithPreparedCallback([this, session, context = std::move(context), ticket = std::move(ticket)](PreparedQueryResult result) mutable
|
||||
{
|
||||
Battlenet::JSON::Login::LoginRefreshResult loginRefreshResult;
|
||||
JSON::Login::LoginRefreshResult loginRefreshResult;
|
||||
if (result)
|
||||
{
|
||||
uint32 loginTicketExpiry = (*result)[0].GetUInt32();
|
||||
@@ -456,7 +382,7 @@ int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncReques
|
||||
|
||||
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION);
|
||||
stmt->setUInt32(0, uint32(now + _loginTicketDuration));
|
||||
stmt->setString(1, request->GetClient()->userid);
|
||||
stmt->setString(1, ticket);
|
||||
LoginDatabase.Execute(stmt);
|
||||
}
|
||||
else
|
||||
@@ -465,34 +391,12 @@ int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncReques
|
||||
else
|
||||
loginRefreshResult.set_is_expired(true);
|
||||
|
||||
SendResponse(request->GetClient(), loginRefreshResult);
|
||||
})));
|
||||
context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
|
||||
context.response.body() = ::JSON::Serialize(loginRefreshResult);
|
||||
session->SendResponse(context);
|
||||
}));
|
||||
|
||||
Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
|
||||
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message const& response)
|
||||
{
|
||||
std::string jsonResponse = JSON::Serialize(response);
|
||||
|
||||
soap_response(soapClient, SOAP_FILE);
|
||||
soap_send_raw(soapClient, jsonResponse.c_str(), jsonResponse.length());
|
||||
return soap_end_send(soapClient);
|
||||
}
|
||||
|
||||
void LoginRESTService::HandleAsyncRequest(std::shared_ptr<AsyncRequest> request)
|
||||
{
|
||||
if (!request->InvokeIfReady())
|
||||
{
|
||||
Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
|
||||
}
|
||||
else if (request->GetResponseStatus())
|
||||
{
|
||||
ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = request->GetResponseStatus();
|
||||
SendResponse(request->GetClient(), Battlenet::JSON::Login::ErrorResponse());
|
||||
}
|
||||
return RequestHandlerResult::Async;
|
||||
}
|
||||
|
||||
std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password)
|
||||
@@ -510,85 +414,8 @@ std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std:
|
||||
return ByteArrayToHexStr(sha.GetDigest(), true);
|
||||
}
|
||||
|
||||
Namespace namespaces[] =
|
||||
void LoginRESTService::OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex)
|
||||
{
|
||||
{ nullptr, nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
LoginRESTService& LoginRESTService::Instance()
|
||||
{
|
||||
static LoginRESTService instance;
|
||||
return instance;
|
||||
sLoginService.OnSocketOpen(std::move(sock), threadIndex);
|
||||
}
|
||||
|
||||
char const* const LoginRESTService::ResponseCodePlugin::PluginId = "bnet-error-code";
|
||||
|
||||
int32 LoginRESTService::ResponseCodePlugin::Init(soap* s, soap_plugin* p, void* /*arg*/)
|
||||
{
|
||||
ResponseCodePlugin* data = new ResponseCodePlugin();
|
||||
data->fresponse = s->fresponse;
|
||||
|
||||
p->id = PluginId;
|
||||
p->fcopy = &Copy;
|
||||
p->fdelete = &Destroy;
|
||||
p->data = data;
|
||||
|
||||
s->fresponse = &ChangeResponse;
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::ResponseCodePlugin::Copy(soap* /*s*/, soap_plugin* dst, soap_plugin* src)
|
||||
{
|
||||
dst->data = new ResponseCodePlugin(*reinterpret_cast<ResponseCodePlugin*>(src->data));
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
void LoginRESTService::ResponseCodePlugin::Destroy(soap* s, soap_plugin* p)
|
||||
{
|
||||
ResponseCodePlugin* data = reinterpret_cast<ResponseCodePlugin*>(p->data);
|
||||
s->fresponse = data->fresponse;
|
||||
delete data;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::ResponseCodePlugin::ChangeResponse(soap* s, int32 originalResponse, uint64 contentLength)
|
||||
{
|
||||
ResponseCodePlugin* self = reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId));
|
||||
return self->fresponse(s, self->ErrorCode && originalResponse == SOAP_FILE ? self->ErrorCode : originalResponse, contentLength);
|
||||
}
|
||||
|
||||
LoginRESTService::ResponseCodePlugin* LoginRESTService::ResponseCodePlugin::GetForClient(soap* s)
|
||||
{
|
||||
return ASSERT_NOTNULL(reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId)));
|
||||
}
|
||||
|
||||
char const* const LoginRESTService::ContentTypePlugin::PluginId = "bnet-content-type";
|
||||
|
||||
int32 LoginRESTService::ContentTypePlugin::Init(soap* s, soap_plugin* p, void* arg)
|
||||
{
|
||||
ContentTypePlugin* data = new ContentTypePlugin();
|
||||
data->fposthdr = s->fposthdr;
|
||||
data->ContentType = reinterpret_cast<char const*>(arg);
|
||||
|
||||
p->id = PluginId;
|
||||
p->fdelete = &Destroy;
|
||||
p->data = data;
|
||||
|
||||
s->fposthdr = &OnSetHeader;
|
||||
return SOAP_OK;
|
||||
}
|
||||
|
||||
void LoginRESTService::ContentTypePlugin::Destroy(soap* s, soap_plugin* p)
|
||||
{
|
||||
ContentTypePlugin* data = reinterpret_cast<ContentTypePlugin*>(p->data);
|
||||
s->fposthdr = data->fposthdr;
|
||||
delete data;
|
||||
}
|
||||
|
||||
int32 LoginRESTService::ContentTypePlugin::OnSetHeader(soap* s, char const* key, char const* value)
|
||||
{
|
||||
ContentTypePlugin* self = reinterpret_cast<ContentTypePlugin*>(soap_lookup_plugin(s, PluginId));
|
||||
if (key && !strcmp("Content-Type", key))
|
||||
value = self->ContentType;
|
||||
|
||||
return self->fposthdr(s, key, value);
|
||||
}
|
||||
|
||||
@@ -18,98 +18,59 @@
|
||||
#ifndef LoginRESTService_h__
|
||||
#define LoginRESTService_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include "IoContext.h"
|
||||
#include "HttpService.h"
|
||||
#include "Login.pb.h"
|
||||
#include "Session.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
class AsyncRequest;
|
||||
struct soap;
|
||||
struct soap_plugin;
|
||||
#include "LoginHttpSession.h"
|
||||
|
||||
namespace Battlenet
|
||||
{
|
||||
enum class BanMode
|
||||
{
|
||||
BAN_IP = 0,
|
||||
BAN_ACCOUNT = 1
|
||||
};
|
||||
|
||||
class LoginRESTService
|
||||
class LoginRESTService : public Trinity::Net::Http::HttpService<LoginHttpSession>
|
||||
{
|
||||
public:
|
||||
LoginRESTService() : _ioContext(nullptr), _stopped(false), _port(0), _loginTicketDuration(0) { }
|
||||
using RequestHandlerResult = Trinity::Net::Http::RequestHandlerResult;
|
||||
using HttpRequest = Trinity::Net::Http::Request;
|
||||
using HttpResponse = Trinity::Net::Http::Response;
|
||||
using HttpRequestContext = Trinity::Net::Http::RequestContext;
|
||||
using HttpSessionState = Trinity::Net::Http::SessionState;
|
||||
|
||||
LoginRESTService() : HttpService("login"), _port(0), _loginTicketDuration(0) { }
|
||||
|
||||
static LoginRESTService& Instance();
|
||||
|
||||
bool Start(Trinity::Asio::IoContext* ioContext);
|
||||
void Stop();
|
||||
bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override;
|
||||
|
||||
std::string const& GetHostnameForClient(boost::asio::ip::address const& address) const;
|
||||
int32 GetPort() const { return _port; }
|
||||
uint16 GetPort() const { return _port; }
|
||||
|
||||
private:
|
||||
void Run();
|
||||
static void OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex);
|
||||
|
||||
friend int32 handle_get_plugin(soap* soapClient);
|
||||
friend int32 handle_post_plugin(soap* soapClient);
|
||||
static std::string ExtractAuthorization(HttpRequest const& request);
|
||||
|
||||
using HttpMethodHandlerMap = std::unordered_map<std::string, int32(LoginRESTService::*)(std::shared_ptr<AsyncRequest>)>;
|
||||
int32 HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers);
|
||||
RequestHandlerResult HandleGetForm(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
|
||||
RequestHandlerResult HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
|
||||
RequestHandlerResult HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
|
||||
|
||||
int32 HandleGetForm(std::shared_ptr<AsyncRequest> request);
|
||||
int32 HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request);
|
||||
int32 HandleGetPortal(std::shared_ptr<AsyncRequest> request);
|
||||
RequestHandlerResult HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
|
||||
RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
|
||||
|
||||
int32 HandlePostLogin(std::shared_ptr<AsyncRequest> request);
|
||||
int32 HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request);
|
||||
static std::string CalculateShaPassHash(std::string const& name, std::string const& password);
|
||||
|
||||
int32 SendResponse(soap* soapClient, google::protobuf::Message const& response);
|
||||
|
||||
void HandleAsyncRequest(std::shared_ptr<AsyncRequest> request);
|
||||
|
||||
std::string CalculateShaPassHash(std::string const& name, std::string const& password);
|
||||
|
||||
struct ResponseCodePlugin
|
||||
{
|
||||
static char const* const PluginId;
|
||||
static int32 Init(soap* s, soap_plugin*, void*);
|
||||
static int32 Copy(soap* s, soap_plugin* dst, soap_plugin* src);
|
||||
static void Destroy(soap* s, soap_plugin* p);
|
||||
static int32 ChangeResponse(soap* s, int32 originalResponse, uint64 contentLength);
|
||||
|
||||
static ResponseCodePlugin* GetForClient(soap* s);
|
||||
|
||||
int32(*fresponse)(soap* s, int32 status, uint64 length);
|
||||
int32 ErrorCode;
|
||||
};
|
||||
|
||||
struct ContentTypePlugin
|
||||
{
|
||||
static char const* const PluginId;
|
||||
static int32 Init(soap* s, soap_plugin* p, void*);
|
||||
static void Destroy(soap* s, soap_plugin* p);
|
||||
static int32 OnSetHeader(soap* s, char const* key, char const* value);
|
||||
|
||||
int32(*fposthdr)(soap* s, char const* key, char const* value);
|
||||
char const* ContentType;
|
||||
};
|
||||
|
||||
Trinity::Asio::IoContext* _ioContext;
|
||||
std::thread _thread;
|
||||
std::atomic<bool> _stopped;
|
||||
Battlenet::JSON::Login::FormInputs _formInputs;
|
||||
JSON::Login::FormInputs _formInputs;
|
||||
std::string _bindIP;
|
||||
int32 _port;
|
||||
uint16 _port;
|
||||
std::array<std::string, 2> _hostnames;
|
||||
std::array<boost::asio::ip::address, 2> _addresses;
|
||||
uint32 _loginTicketDuration;
|
||||
|
||||
HttpMethodHandlerMap _getHandlers;
|
||||
HttpMethodHandlerMap _postHandlers;
|
||||
};
|
||||
}
|
||||
|
||||
#define sLoginService LoginRESTService::Instance()
|
||||
#define sLoginService Battlenet::LoginRESTService::Instance()
|
||||
|
||||
#endif // LoginRESTService_h__
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "RealmList.h"
|
||||
#include "RealmList.pb.h"
|
||||
#include "ServiceDispatcher.h"
|
||||
#include "SslContext.h"
|
||||
#include "Timezone.h"
|
||||
#include <rapidjson/document.h>
|
||||
#include <zlib.h>
|
||||
@@ -73,7 +74,8 @@ void Battlenet::Session::GameAccountInfo::LoadResult(Field const* fields)
|
||||
DisplayName = Name;
|
||||
}
|
||||
|
||||
Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket)), _accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(),
|
||||
Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket), SslContext::instance()),
|
||||
_accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(),
|
||||
_os(), _build(0), _timezoneOffset(0min), _ipCountry(), _clientSecret(), _authed(false), _requestToken(0)
|
||||
{
|
||||
_headerLengthBuffer.Resize(2);
|
||||
|
||||
@@ -20,11 +20,10 @@
|
||||
|
||||
#include "AsyncCallbackProcessor.h"
|
||||
#include "Duration.h"
|
||||
#include "Realm.h"
|
||||
#include "SslContext.h"
|
||||
#include "SslSocket.h"
|
||||
#include "Socket.h"
|
||||
#include "QueryResult.h"
|
||||
#include "Realm.h"
|
||||
#include "Socket.h"
|
||||
#include "SslSocket.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <google/protobuf/message.h>
|
||||
#include <memory>
|
||||
@@ -65,9 +64,9 @@ using namespace bgs::protocol;
|
||||
|
||||
namespace Battlenet
|
||||
{
|
||||
class Session : public Socket<Session, SslSocket<SslContext>>
|
||||
class Session : public Socket<Session, SslSocket<>>
|
||||
{
|
||||
typedef Socket<Session, SslSocket<SslContext>> BattlenetSocket;
|
||||
typedef Socket<Session, SslSocket<>> BattlenetSocket;
|
||||
|
||||
public:
|
||||
struct LastPlayedCharacterInfo
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "MySQLWorkaround.h"
|
||||
#include <boost/asio/use_future.hpp>
|
||||
#include <mysqld_error.h>
|
||||
#include <utility>
|
||||
#ifdef TRINITY_DEBUG
|
||||
#include <sstream>
|
||||
#include <boost/stacktrace.hpp>
|
||||
|
||||
@@ -45,8 +45,7 @@ target_link_libraries(shared
|
||||
database
|
||||
rapidjson
|
||||
proto
|
||||
zlib
|
||||
gsoap)
|
||||
zlib)
|
||||
|
||||
set_target_properties(shared
|
||||
PROPERTIES
|
||||
|
||||
115
src/server/shared/Networking/Http/BaseHttpSocket.cpp
Normal file
115
src/server/shared/Networking/Http/BaseHttpSocket.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BaseHttpSocket.h"
|
||||
#include <boost/beast/http/serializer.hpp>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
using RequestSerializer = boost::beast::http::request_serializer<ResponseBody>;
|
||||
using ResponseSerializer = boost::beast::http::response_serializer<ResponseBody>;
|
||||
|
||||
bool AbstractSocket::ParseRequest(MessageBuffer& packet, RequestParser& parser)
|
||||
{
|
||||
if (!parser.is_done())
|
||||
{
|
||||
// need more data in the payload
|
||||
boost::system::error_code ec = {};
|
||||
std::size_t readDataSize = parser.put(boost::asio::const_buffer(packet.GetReadPointer(), packet.GetActiveSize()), ec);
|
||||
packet.ReadCompleted(readDataSize);
|
||||
}
|
||||
|
||||
return parser.is_done();
|
||||
}
|
||||
|
||||
std::string AbstractSocket::SerializeRequest(Request const& request)
|
||||
{
|
||||
RequestSerializer serializer(request);
|
||||
|
||||
std::string buffer;
|
||||
while (!serializer.is_done())
|
||||
{
|
||||
size_t totalBytes = 0;
|
||||
boost::system::error_code ec = {};
|
||||
serializer.next(ec, [&]<typename ConstBufferSequence>(boost::system::error_code const&, ConstBufferSequence const& buffers)
|
||||
{
|
||||
size_t totalBytesInBuffers = boost::asio::buffer_size(buffers);
|
||||
|
||||
buffer.reserve(buffer.size() + totalBytes);
|
||||
|
||||
auto begin = boost::asio::buffers_begin(buffers);
|
||||
auto end = boost::asio::buffers_end(buffers);
|
||||
|
||||
std::copy(begin, end, std::back_inserter(buffer));
|
||||
totalBytes += totalBytesInBuffers;
|
||||
});
|
||||
|
||||
serializer.consume(totalBytes);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
MessageBuffer AbstractSocket::SerializeResponse(Request const& request, Response& response)
|
||||
{
|
||||
response.prepare_payload();
|
||||
|
||||
ResponseSerializer serializer(response);
|
||||
bool (*serializerIsDone)(ResponseSerializer&);
|
||||
if (request.method() != boost::beast::http::verb::head)
|
||||
{
|
||||
serializerIsDone = [](ResponseSerializer& s) { return s.is_done(); };
|
||||
}
|
||||
else
|
||||
{
|
||||
serializerIsDone = [](ResponseSerializer& s) { return s.is_header_done(); };
|
||||
serializer.split(true);
|
||||
}
|
||||
|
||||
MessageBuffer buffer;
|
||||
while (!serializerIsDone(serializer))
|
||||
{
|
||||
serializer.limit(buffer.GetRemainingSpace());
|
||||
|
||||
size_t totalBytes = 0;
|
||||
boost::system::error_code ec = {};
|
||||
serializer.next(ec, [&]<typename ConstBufferSequence>(boost::system::error_code& currentError, ConstBufferSequence const& buffers)
|
||||
{
|
||||
size_t totalBytesInBuffers = boost::asio::buffer_size(buffers);
|
||||
if (totalBytesInBuffers > buffer.GetRemainingSpace())
|
||||
{
|
||||
currentError = boost::beast::http::error::need_more;
|
||||
return;
|
||||
}
|
||||
|
||||
auto begin = boost::asio::buffers_begin(buffers);
|
||||
auto end = boost::asio::buffers_end(buffers);
|
||||
|
||||
std::copy(begin, end, buffer.GetWritePointer());
|
||||
buffer.WriteCompleted(totalBytesInBuffers);
|
||||
totalBytes += totalBytesInBuffers;
|
||||
});
|
||||
|
||||
serializer.consume(totalBytes);
|
||||
|
||||
if (ec == boost::beast::http::error::need_more)
|
||||
buffer.Resize(buffer.GetBufferSize() + 4096);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
191
src/server/shared/Networking/Http/BaseHttpSocket.h
Normal file
191
src/server/shared/Networking/Http/BaseHttpSocket.h
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_BASE_HTTP_SOCKET_H
|
||||
#define TRINITYCORE_BASE_HTTP_SOCKET_H
|
||||
|
||||
#include "AsyncCallbackProcessor.h"
|
||||
#include "DatabaseEnvFwd.h"
|
||||
#include "HttpCommon.h"
|
||||
#include "HttpSessionState.h"
|
||||
#include "Optional.h"
|
||||
#include "QueryCallback.h"
|
||||
#include "Socket.h"
|
||||
#include <boost/asio/buffers_iterator.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
using RequestParser = boost::beast::http::request_parser<RequestBody>;
|
||||
|
||||
class TC_SHARED_API AbstractSocket
|
||||
{
|
||||
public:
|
||||
AbstractSocket() = default;
|
||||
AbstractSocket(AbstractSocket const& other) = default;
|
||||
AbstractSocket(AbstractSocket&& other) = default;
|
||||
AbstractSocket& operator=(AbstractSocket const& other) = default;
|
||||
AbstractSocket& operator=(AbstractSocket&& other) = default;
|
||||
virtual ~AbstractSocket() = default;
|
||||
|
||||
static bool ParseRequest(MessageBuffer& packet, RequestParser& parser);
|
||||
|
||||
static std::string SerializeRequest(Request const& request);
|
||||
static MessageBuffer SerializeResponse(Request const& request, Response& response);
|
||||
|
||||
virtual void SendResponse(RequestContext& context) = 0;
|
||||
|
||||
virtual void QueueQuery(QueryCallback&& queryCallback) = 0;
|
||||
|
||||
virtual std::string GetClientInfo() const = 0;
|
||||
|
||||
virtual Optional<boost::uuids::uuid> GetSessionId() const = 0;
|
||||
};
|
||||
|
||||
template<typename Derived, typename Stream>
|
||||
class BaseSocket : public ::Socket<Derived, Stream>, public AbstractSocket
|
||||
{
|
||||
using Base = ::Socket<Derived, Stream>;
|
||||
|
||||
public:
|
||||
template<typename... Args>
|
||||
explicit BaseSocket(boost::asio::ip::tcp::socket&& socket, Args&&... args)
|
||||
: Base(std::move(socket), std::forward<Args>(args)...) { }
|
||||
|
||||
BaseSocket(BaseSocket const& other) = delete;
|
||||
BaseSocket(BaseSocket&& other) = delete;
|
||||
BaseSocket& operator=(BaseSocket const& other) = delete;
|
||||
BaseSocket& operator=(BaseSocket&& other) = delete;
|
||||
|
||||
~BaseSocket() = default;
|
||||
|
||||
void ReadHandler() override
|
||||
{
|
||||
if (!this->IsOpen())
|
||||
return;
|
||||
|
||||
MessageBuffer& packet = this->GetReadBuffer();
|
||||
while (packet.GetActiveSize() > 0)
|
||||
{
|
||||
if (!ParseRequest(packet, *_httpParser))
|
||||
{
|
||||
// Couldn't receive the whole data this time.
|
||||
break;
|
||||
}
|
||||
|
||||
if (!HandleMessage(_httpParser->get()))
|
||||
{
|
||||
this->CloseSocket();
|
||||
break;
|
||||
}
|
||||
|
||||
this->ResetHttpParser();
|
||||
}
|
||||
|
||||
this->AsyncRead();
|
||||
}
|
||||
|
||||
bool HandleMessage(Request& request)
|
||||
{
|
||||
RequestContext context { .request = std::move(request) };
|
||||
|
||||
if (!_state)
|
||||
_state = this->ObtainSessionState(context);
|
||||
|
||||
RequestHandlerResult status = this->RequestHandler(context);
|
||||
|
||||
if (status != RequestHandlerResult::Async)
|
||||
this->SendResponse(context);
|
||||
|
||||
return status != RequestHandlerResult::Error;
|
||||
}
|
||||
|
||||
virtual RequestHandlerResult RequestHandler(RequestContext& context) = 0;
|
||||
|
||||
void SendResponse(RequestContext& context) override
|
||||
{
|
||||
MessageBuffer buffer = SerializeResponse(context.request, context.response);
|
||||
|
||||
TC_LOG_DEBUG("server.http", "{} Request {} {} done, status {}", this->GetClientInfo(), ToStdStringView(context.request.method_string()),
|
||||
ToStdStringView(context.request.target()), context.response.result_int());
|
||||
if (sLog->ShouldLog("server.http", LOG_LEVEL_TRACE))
|
||||
{
|
||||
sLog->OutMessage("server.http", LOG_LEVEL_TRACE, "{} Request: ", this->GetClientInfo(),
|
||||
CanLogRequestContent(context) ? SerializeRequest(context.request) : "<REDACTED>");
|
||||
sLog->OutMessage("server.http", LOG_LEVEL_TRACE, "{} Response: ", this->GetClientInfo(),
|
||||
CanLogResponseContent(context) ? std::string_view(reinterpret_cast<char const*>(buffer.GetBasePointer()), buffer.GetActiveSize()) : "<REDACTED>");
|
||||
}
|
||||
|
||||
this->QueuePacket(std::move(buffer));
|
||||
|
||||
if (!context.response.keep_alive())
|
||||
this->DelayedCloseSocket();
|
||||
}
|
||||
|
||||
void QueueQuery(QueryCallback&& queryCallback) override
|
||||
{
|
||||
this->_queryProcessor.AddCallback(std::move(queryCallback));
|
||||
}
|
||||
|
||||
bool Update() override
|
||||
{
|
||||
if (!this->Base::Update())
|
||||
return false;
|
||||
|
||||
this->_queryProcessor.ProcessReadyCallbacks();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetClientInfo() const override
|
||||
{
|
||||
std::string info;
|
||||
info.reserve(500);
|
||||
auto itr = StringFormatTo(std::back_inserter(info), "[{}:{}", this->GetRemoteIpAddress().to_string(), this->GetRemotePort());
|
||||
if (_state)
|
||||
itr = StringFormatTo(itr, ", Session Id: {}", boost::uuids::to_string(_state->Id));
|
||||
|
||||
StringFormatTo(itr, "]");
|
||||
return info;
|
||||
}
|
||||
|
||||
Optional<boost::uuids::uuid> GetSessionId() const final
|
||||
{
|
||||
if (this->_state)
|
||||
return this->_state->Id;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
void ResetHttpParser()
|
||||
{
|
||||
this->_httpParser.reset();
|
||||
this->_httpParser.emplace();
|
||||
this->_httpParser->eager(true);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<SessionState> ObtainSessionState(RequestContext& context) const = 0;
|
||||
|
||||
QueryCallbackProcessor _queryProcessor;
|
||||
Optional<RequestParser> _httpParser;
|
||||
std::shared_ptr<SessionState> _state;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_BASE_HTTP_SOCKET_H
|
||||
55
src/server/shared/Networking/Http/HttpCommon.h
Normal file
55
src/server/shared/Networking/Http/HttpCommon.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HTTP_COMMON_H
|
||||
#define TRINITYCORE_HTTP_COMMON_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
using RequestBody = boost::beast::http::string_body;
|
||||
using ResponseBody = boost::beast::http::string_body;
|
||||
|
||||
using Request = boost::beast::http::request<RequestBody>;
|
||||
using Response = boost::beast::http::response<ResponseBody>;
|
||||
|
||||
struct RequestContext
|
||||
{
|
||||
Request request;
|
||||
Response response;
|
||||
struct RequestHandler const* handler = nullptr;
|
||||
};
|
||||
|
||||
TC_SHARED_API bool CanLogRequestContent(RequestContext const& context);
|
||||
TC_SHARED_API bool CanLogResponseContent(RequestContext const& context);
|
||||
|
||||
inline std::string_view ToStdStringView(boost::beast::string_view bsw)
|
||||
{
|
||||
return { bsw.data(), bsw.size() };
|
||||
}
|
||||
|
||||
enum class RequestHandlerResult
|
||||
{
|
||||
Handled,
|
||||
Error,
|
||||
Async,
|
||||
};
|
||||
}
|
||||
#endif // TRINITYCORE_HTTP_COMMON_H
|
||||
258
src/server/shared/Networking/Http/HttpService.cpp
Normal file
258
src/server/shared/Networking/Http/HttpService.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HttpService.h"
|
||||
#include "BaseHttpSocket.h"
|
||||
#include "CryptoRandom.h"
|
||||
#include "Timezone.h"
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/uuid/string_generator.hpp>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
bool CanLogRequestContent(RequestContext const& context)
|
||||
{
|
||||
return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogRequestContent);
|
||||
}
|
||||
|
||||
bool CanLogResponseContent(RequestContext const& context)
|
||||
{
|
||||
return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogResponseContent);
|
||||
}
|
||||
|
||||
RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context)
|
||||
{
|
||||
TC_LOG_DEBUG(_logger, "{} Starting request {} {}", session->GetClientInfo(),
|
||||
ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target()));
|
||||
|
||||
std::string_view path = [&]
|
||||
{
|
||||
std::string_view path = ToStdStringView(context.request.target());
|
||||
size_t queryIndex = path.find('?');
|
||||
if (queryIndex != std::string_view::npos)
|
||||
path = path.substr(0, queryIndex);
|
||||
return path;
|
||||
}();
|
||||
|
||||
context.handler = [&]() -> HttpMethodHandlerMap::mapped_type const*
|
||||
{
|
||||
switch (context.request.method())
|
||||
{
|
||||
case boost::beast::http::verb::get:
|
||||
case boost::beast::http::verb::head:
|
||||
{
|
||||
auto itr = _getHandlers.find(path);
|
||||
return itr != _getHandlers.end() ? &itr->second : nullptr;
|
||||
}
|
||||
case boost::beast::http::verb::post:
|
||||
{
|
||||
auto itr = _postHandlers.find(path);
|
||||
return itr != _postHandlers.end() ? &itr->second : nullptr;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
SystemTimePoint responseDate = SystemTimePoint::clock::now();
|
||||
context.response.set(boost::beast::http::field::date, StringFormat("{:%a, %d %b %Y %T GMT}", responseDate - Timezone::GetSystemZoneOffsetAt(responseDate)));
|
||||
context.response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
context.response.keep_alive(context.response.keep_alive());
|
||||
|
||||
if (!context.handler)
|
||||
return HandlePathNotFound(std::move(session), context);
|
||||
|
||||
return context.handler->Func(std::move(session), context);
|
||||
}
|
||||
|
||||
RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
|
||||
{
|
||||
context.response.result(boost::beast::http::status::not_found);
|
||||
return RequestHandlerResult::Handled;
|
||||
}
|
||||
|
||||
RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
|
||||
{
|
||||
context.response.result(boost::beast::http::status::unauthorized);
|
||||
return RequestHandlerResult::Handled;
|
||||
}
|
||||
|
||||
void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path,
|
||||
std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler,
|
||||
RequestHandlerFlag flags)
|
||||
{
|
||||
HttpMethodHandlerMap& handlerMap = [&]() -> HttpMethodHandlerMap&
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case boost::beast::http::verb::get:
|
||||
return _getHandlers;
|
||||
case boost::beast::http::verb::post:
|
||||
return _postHandlers;
|
||||
default:
|
||||
{
|
||||
std::string_view methodString = ToStdStringView(boost::beast::http::to_string(method));
|
||||
ABORT_MSG("Tried to register a handler for unsupported HTTP method " STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(methodString));
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
handlerMap[std::string(path)] = { .Func = std::move(handler), .Flags = flags };
|
||||
TC_LOG_INFO(_logger, "Registered new handler for {} {}", ToStdStringView(boost::beast::http::to_string(method)), path);
|
||||
}
|
||||
|
||||
void SessionService::InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address)
|
||||
{
|
||||
state->RemoteAddress = address;
|
||||
|
||||
// Generate session id
|
||||
{
|
||||
std::unique_lock lock{ _sessionsMutex };
|
||||
|
||||
while (state->Id.is_nil() || _sessions.contains(state->Id))
|
||||
std::copy_n(Trinity::Crypto::GetRandomBytes<16>().begin(), 16, state->Id.begin());
|
||||
|
||||
TC_LOG_DEBUG(_logger, "Client at {} created new session {}", address.to_string(), boost::uuids::to_string(state->Id));
|
||||
_sessions[state->Id] = std::move(state);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionService::Start(Asio::IoContext& ioContext)
|
||||
{
|
||||
_inactiveSessionsKillTimer = std::make_unique<Asio::DeadlineTimer>(ioContext);
|
||||
_inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1));
|
||||
_inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err)
|
||||
{
|
||||
if (err)
|
||||
return;
|
||||
|
||||
KillInactiveSessions();
|
||||
});
|
||||
}
|
||||
|
||||
void SessionService::Stop()
|
||||
{
|
||||
_inactiveSessionsKillTimer = nullptr;
|
||||
{
|
||||
std::unique_lock lock{ _sessionsMutex };
|
||||
_sessions.clear();
|
||||
}
|
||||
{
|
||||
std::unique_lock lock{ _inactiveSessionsMutex };
|
||||
_inactiveSessions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SessionState> SessionService::FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address)
|
||||
{
|
||||
std::shared_ptr<SessionState> state;
|
||||
|
||||
{
|
||||
std::shared_lock lock{ _sessionsMutex };
|
||||
auto itr = _sessions.find(boost::uuids::string_generator()(id.begin(), id.end()));
|
||||
if (itr == _sessions.end())
|
||||
{
|
||||
TC_LOG_DEBUG(_logger, "Client at {} attempted to use a session {} that was expired", address.to_string(), id);
|
||||
return nullptr; // no session
|
||||
}
|
||||
|
||||
state = itr->second;
|
||||
}
|
||||
|
||||
if (state->RemoteAddress != address)
|
||||
{
|
||||
TC_LOG_ERROR(_logger, "Client at {} attempted to use a session {} that was last accessed from {}, denied access",
|
||||
address.to_string(), id, state->RemoteAddress.to_string());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex };
|
||||
_inactiveSessions.erase(state->Id);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void SessionService::MarkSessionInactive(boost::uuids::uuid const& id)
|
||||
{
|
||||
{
|
||||
std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex };
|
||||
_inactiveSessions.insert(id);
|
||||
}
|
||||
|
||||
{
|
||||
auto itr = _sessions.find(id);
|
||||
if (itr != _sessions.end())
|
||||
{
|
||||
itr->second->InactiveTimestamp = TimePoint::clock::now() + Minutes(5);
|
||||
TC_LOG_TRACE(_logger, "Session {} marked as inactive", boost::uuids::to_string(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionService::KillInactiveSessions()
|
||||
{
|
||||
std::set<boost::uuids::uuid> inactiveSessions;
|
||||
|
||||
{
|
||||
std::unique_lock lock{ _inactiveSessionsMutex };
|
||||
std::swap(_inactiveSessions, inactiveSessions);
|
||||
}
|
||||
|
||||
{
|
||||
TimePoint now = TimePoint::clock::now();
|
||||
std::size_t inactiveSessionsCount = inactiveSessions.size();
|
||||
|
||||
std::unique_lock lock{ _sessionsMutex };
|
||||
for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); )
|
||||
{
|
||||
auto sessionItr = _sessions.find(*itr);
|
||||
if (sessionItr == _sessions.end() || sessionItr->second->InactiveTimestamp < now)
|
||||
{
|
||||
_sessions.erase(sessionItr);
|
||||
itr = inactiveSessions.erase(itr);
|
||||
}
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG(_logger, "Killed {} inactive sessions", inactiveSessionsCount - inactiveSessions.size());
|
||||
}
|
||||
|
||||
{
|
||||
// restore sessions not killed to inactive queue
|
||||
std::unique_lock lock{ _inactiveSessionsMutex };
|
||||
for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); )
|
||||
{
|
||||
auto node = inactiveSessions.extract(itr++);
|
||||
_inactiveSessions.insert(std::move(node));
|
||||
}
|
||||
}
|
||||
|
||||
_inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1));
|
||||
_inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err)
|
||||
{
|
||||
if (err)
|
||||
return;
|
||||
|
||||
KillInactiveSessions();
|
||||
});
|
||||
}
|
||||
}
|
||||
188
src/server/shared/Networking/Http/HttpService.h
Normal file
188
src/server/shared/Networking/Http/HttpService.h
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HTTP_SERVICE_H
|
||||
#define TRINITYCORE_HTTP_SERVICE_H
|
||||
|
||||
#include "AsioHacksFwd.h"
|
||||
#include "Concepts.h"
|
||||
#include "Define.h"
|
||||
#include "EnumFlag.h"
|
||||
#include "HttpCommon.h"
|
||||
#include "HttpSessionState.h"
|
||||
#include "Optional.h"
|
||||
#include "SocketMgr.h"
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
class AbstractSocket;
|
||||
|
||||
enum class RequestHandlerFlag
|
||||
{
|
||||
None = 0x0,
|
||||
DoNotLogRequestContent = 0x1,
|
||||
DoNotLogResponseContent = 0x2,
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG(RequestHandlerFlag);
|
||||
|
||||
struct RequestHandler
|
||||
{
|
||||
std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> Func;
|
||||
EnumFlag<RequestHandlerFlag> Flags = RequestHandlerFlag::None;
|
||||
};
|
||||
|
||||
class TC_SHARED_API DispatcherService
|
||||
{
|
||||
public:
|
||||
explicit DispatcherService(std::string_view loggerSuffix) : _logger("server.http.dispatcher.")
|
||||
{
|
||||
_logger.append(loggerSuffix);
|
||||
}
|
||||
|
||||
RequestHandlerResult HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context);
|
||||
|
||||
RequestHandlerResult HandlePathNotFound(std::shared_ptr<AbstractSocket> session, RequestContext& context);
|
||||
RequestHandlerResult HandleUnauthorized(std::shared_ptr<AbstractSocket> session, RequestContext& context);
|
||||
|
||||
protected:
|
||||
void RegisterHandler(boost::beast::http::verb method, std::string_view path,
|
||||
std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler,
|
||||
RequestHandlerFlag flags = RequestHandlerFlag::None);
|
||||
|
||||
private:
|
||||
using HttpMethodHandlerMap = std::map<std::string, RequestHandler, std::less<>>;
|
||||
|
||||
HttpMethodHandlerMap _getHandlers;
|
||||
HttpMethodHandlerMap _postHandlers;
|
||||
|
||||
std::string _logger;
|
||||
};
|
||||
|
||||
class TC_SHARED_API SessionService
|
||||
{
|
||||
public:
|
||||
explicit SessionService(std::string_view loggerSuffix) : _logger("server.http.session.")
|
||||
{
|
||||
_logger.append(loggerSuffix);
|
||||
}
|
||||
|
||||
void Start(Asio::IoContext& ioContext);
|
||||
void Stop();
|
||||
|
||||
std::shared_ptr<SessionState> FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address);
|
||||
void MarkSessionInactive(boost::uuids::uuid const& id);
|
||||
|
||||
protected:
|
||||
void InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address);
|
||||
|
||||
void KillInactiveSessions();
|
||||
|
||||
private:
|
||||
std::shared_mutex _sessionsMutex;
|
||||
std::map<boost::uuids::uuid, std::shared_ptr<SessionState>> _sessions;
|
||||
|
||||
std::mutex _inactiveSessionsMutex;
|
||||
std::set<boost::uuids::uuid> _inactiveSessions;
|
||||
std::unique_ptr<Asio::DeadlineTimer> _inactiveSessionsKillTimer;
|
||||
|
||||
std::string _logger;
|
||||
};
|
||||
|
||||
template<typename Callable, typename SessionImpl>
|
||||
concept HttpRequestHandler = invocable_r<Callable, RequestHandlerResult, std::shared_ptr<SessionImpl>, RequestContext&>;
|
||||
|
||||
template<typename SessionImpl>
|
||||
class HttpService : public SocketMgr<SessionImpl>, public DispatcherService, public SessionService
|
||||
{
|
||||
public:
|
||||
HttpService(std::string_view loggerSuffix) : DispatcherService(loggerSuffix), SessionService(loggerSuffix), _ioContext(nullptr), _logger("server.http.")
|
||||
{
|
||||
_logger.append(loggerSuffix);
|
||||
}
|
||||
|
||||
bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override
|
||||
{
|
||||
if (!SocketMgr<SessionImpl>::StartNetwork(ioContext, bindIp, port, threadCount))
|
||||
return false;
|
||||
|
||||
SessionService::Start(ioContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
void StopNetwork() override
|
||||
{
|
||||
SessionService::Stop();
|
||||
SocketMgr<SessionImpl>::StopNetwork();
|
||||
}
|
||||
|
||||
// http handling
|
||||
using DispatcherService::RegisterHandler;
|
||||
|
||||
template<HttpRequestHandler<SessionImpl> Callable>
|
||||
void RegisterHandler(boost::beast::http::verb method, std::string_view path, Callable handler, RequestHandlerFlag flags = RequestHandlerFlag::None)
|
||||
{
|
||||
this->DispatcherService::RegisterHandler(method, path, [handler = std::move(handler)](std::shared_ptr<AbstractSocket> session, RequestContext& context) -> RequestHandlerResult
|
||||
{
|
||||
return handler(std::static_pointer_cast<SessionImpl>(std::move(session)), context);
|
||||
}, flags);
|
||||
}
|
||||
|
||||
// session tracking
|
||||
virtual std::shared_ptr<SessionState> CreateNewSessionState(boost::asio::ip::address const& address)
|
||||
{
|
||||
std::shared_ptr<SessionState> state = std::make_shared<SessionState>();
|
||||
InitAndStoreSessionState(state, address);
|
||||
return state;
|
||||
}
|
||||
|
||||
protected:
|
||||
class Thread : public NetworkThread<SessionImpl>
|
||||
{
|
||||
protected:
|
||||
void SocketRemoved(std::shared_ptr<SessionImpl> session) override
|
||||
{
|
||||
if (Optional<boost::uuids::uuid> id = session->GetSessionId())
|
||||
_service->MarkSessionInactive(*id);
|
||||
}
|
||||
|
||||
private:
|
||||
friend HttpService;
|
||||
|
||||
SessionService* _service;
|
||||
};
|
||||
|
||||
NetworkThread<SessionImpl>* CreateThreads() const override
|
||||
{
|
||||
Thread* threads = new Thread[this->GetNetworkThreadCount()];
|
||||
for (int32 i = 0; i < this->GetNetworkThreadCount(); ++i)
|
||||
threads[i]._service = const_cast<HttpService*>(this);
|
||||
return threads;
|
||||
}
|
||||
|
||||
private:
|
||||
Asio::IoContext* _ioContext;
|
||||
std::string _logger;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HTTP_SERVICE_H
|
||||
35
src/server/shared/Networking/Http/HttpSessionState.h
Normal file
35
src/server/shared/Networking/Http/HttpSessionState.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HTTP_SESSION_STATE_H
|
||||
#define TRINITYCORE_HTTP_SESSION_STATE_H
|
||||
|
||||
#include "Duration.h"
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
struct SessionState
|
||||
{
|
||||
boost::uuids::uuid Id = { };
|
||||
boost::asio::ip::address RemoteAddress;
|
||||
TimePoint InactiveTimestamp = TimePoint::max();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HTTP_SESSION_STATE_H
|
||||
75
src/server/shared/Networking/Http/HttpSocket.h
Normal file
75
src/server/shared/Networking/Http/HttpSocket.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HTTP_SOCKET_H
|
||||
#define TRINITYCORE_HTTP_SOCKET_H
|
||||
|
||||
#include "BaseHttpSocket.h"
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
namespace Impl
|
||||
{
|
||||
class BoostBeastSocketWrapper : public boost::beast::tcp_stream
|
||||
{
|
||||
public:
|
||||
using boost::beast::tcp_stream::tcp_stream;
|
||||
|
||||
void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError)
|
||||
{
|
||||
socket().shutdown(what, shutdownError);
|
||||
}
|
||||
|
||||
void close(boost::system::error_code& /*error*/)
|
||||
{
|
||||
boost::beast::tcp_stream::close();
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket::endpoint_type remote_endpoint() const
|
||||
{
|
||||
return socket().remote_endpoint();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
class Socket : public BaseSocket<Derived, Impl::BoostBeastSocketWrapper>
|
||||
{
|
||||
using SocketBase = BaseSocket<Derived, Impl::BoostBeastSocketWrapper>;
|
||||
|
||||
public:
|
||||
explicit Socket(boost::asio::ip::tcp::socket&& socket)
|
||||
: SocketBase(std::move(socket)) { }
|
||||
|
||||
Socket(Socket const& other) = delete;
|
||||
Socket(Socket&& other) = delete;
|
||||
Socket& operator=(Socket const& other) = delete;
|
||||
Socket& operator=(Socket&& other) = delete;
|
||||
|
||||
~Socket() = default;
|
||||
|
||||
void Start() override
|
||||
{
|
||||
this->ResetHttpParser();
|
||||
|
||||
this->AsyncRead();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HTTP_SOCKET_H
|
||||
97
src/server/shared/Networking/Http/HttpSslSocket.h
Normal file
97
src/server/shared/Networking/Http/HttpSslSocket.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_HTTP_SSL_SOCKET_H
|
||||
#define TRINITYCORE_HTTP_SSL_SOCKET_H
|
||||
|
||||
#include "BaseHttpSocket.h"
|
||||
#include "SslSocket.h"
|
||||
#include <boost/beast/core/stream_traits.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
namespace Trinity::Net::Http
|
||||
{
|
||||
namespace Impl
|
||||
{
|
||||
class BoostBeastSslSocketWrapper : public ::SslSocket<boost::beast::ssl_stream<boost::beast::tcp_stream>>
|
||||
{
|
||||
public:
|
||||
using SslSocket::SslSocket;
|
||||
|
||||
void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError)
|
||||
{
|
||||
_sslSocket.shutdown(shutdownError);
|
||||
boost::beast::get_lowest_layer(_sslSocket).socket().shutdown(what, shutdownError);
|
||||
}
|
||||
|
||||
void close(boost::system::error_code& /*error*/)
|
||||
{
|
||||
boost::beast::get_lowest_layer(_sslSocket).close();
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket::endpoint_type remote_endpoint() const
|
||||
{
|
||||
return boost::beast::get_lowest_layer(_sslSocket).socket().remote_endpoint();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
class SslSocket : public BaseSocket<Derived, Impl::BoostBeastSslSocketWrapper>
|
||||
{
|
||||
using SocketBase = BaseSocket<Derived, Impl::BoostBeastSslSocketWrapper>;
|
||||
|
||||
public:
|
||||
explicit SslSocket(boost::asio::ip::tcp::socket&& socket, boost::asio::ssl::context& sslContext)
|
||||
: SocketBase(std::move(socket), sslContext) { }
|
||||
|
||||
SslSocket(SslSocket const& other) = delete;
|
||||
SslSocket(SslSocket&& other) = delete;
|
||||
SslSocket& operator=(SslSocket const& other) = delete;
|
||||
SslSocket& operator=(SslSocket&& other) = delete;
|
||||
|
||||
~SslSocket() = default;
|
||||
|
||||
void Start() override
|
||||
{
|
||||
this->AsyncHandshake();
|
||||
}
|
||||
|
||||
void AsyncHandshake()
|
||||
{
|
||||
this->underlying_stream().async_handshake(boost::asio::ssl::stream_base::server,
|
||||
[self = this->shared_from_this()](boost::system::error_code const& error) { self->HandshakeHandler(error); });
|
||||
}
|
||||
|
||||
void HandshakeHandler(boost::system::error_code const& error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
TC_LOG_ERROR("server.http.session.ssl", "{} SSL Handshake failed {}", this->GetClientInfo(), error.message());
|
||||
this->CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
this->ResetHttpParser();
|
||||
|
||||
this->AsyncRead();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_HTTP_SSL_SOCKET_H
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
return _connections;
|
||||
}
|
||||
|
||||
virtual void AddSocket(std::shared_ptr<SocketType> sock)
|
||||
void AddSocket(std::shared_ptr<SocketType> sock)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_newSocketsLock);
|
||||
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
#ifndef __SOCKET_H__
|
||||
#define __SOCKET_H__
|
||||
|
||||
#include "MessageBuffer.h"
|
||||
#include "Log.h"
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "MessageBuffer.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <type_traits>
|
||||
|
||||
#define READ_BLOCK_SIZE 4096
|
||||
#ifdef BOOST_ASIO_HAS_IOCP
|
||||
@@ -63,12 +62,19 @@ template<class T, class Stream = boost::asio::ip::tcp::socket>
|
||||
class Socket : public std::enable_shared_from_this<T>
|
||||
{
|
||||
public:
|
||||
explicit Socket(boost::asio::ip::tcp::socket&& socket) : _socket(std::move(socket)), _remoteAddress(_socket.remote_endpoint().address()),
|
||||
_remotePort(_socket.remote_endpoint().port()), _readBuffer(), _closed(false), _closing(false), _isWritingAsync(false)
|
||||
template<typename... Args>
|
||||
explicit Socket(boost::asio::ip::tcp::socket&& socket, Args&&... args) : _socket(std::move(socket), std::forward<Args>(args)...),
|
||||
_remoteAddress(_socket.remote_endpoint().address()), _remotePort(_socket.remote_endpoint().port()),
|
||||
_closed(false), _closing(false), _isWritingAsync(false)
|
||||
{
|
||||
_readBuffer.Resize(READ_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
Socket(Socket const& other) = delete;
|
||||
Socket(Socket&& other) = delete;
|
||||
Socket& operator=(Socket const& other) = delete;
|
||||
Socket& operator=(Socket&& other) = delete;
|
||||
|
||||
virtual ~Socket()
|
||||
{
|
||||
_closed = true;
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
|
||||
namespace boostssl = boost::asio::ssl;
|
||||
|
||||
template<class SslContext, class Stream = boostssl::stream<boost::asio::ip::tcp::socket>>
|
||||
template<class Stream = boostssl::stream<boost::asio::ip::tcp::socket>>
|
||||
class SslSocket
|
||||
{
|
||||
public:
|
||||
explicit SslSocket(boost::asio::ip::tcp::socket&& socket) : _sslSocket(std::move(socket), SslContext::instance())
|
||||
explicit SslSocket(boost::asio::ip::tcp::socket&& socket, boost::asio::ssl::context& sslContext) : _sslSocket(std::move(socket), sslContext)
|
||||
{
|
||||
_sslSocket.set_verify_mode(boostssl::verify_none);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ target_link_libraries(worldserver
|
||||
PUBLIC
|
||||
scripts
|
||||
game
|
||||
readline)
|
||||
readline
|
||||
gsoap)
|
||||
|
||||
CollectIncludeDirectories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
|
||||
Reference in New Issue
Block a user