From d246ea56bf11246c60afc1f8b015dee926001d9a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 25 Mar 2020 09:00:26 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httpproject/httpclient.c | 245 +++++++++++++++++--- httpproject/httpserver.c | 457 +++++++++++++++++++++++++++++++++----- httpproject/songdata.json | 7 +- 3 files changed, 630 insertions(+), 79 deletions(-) diff --git a/httpproject/httpclient.c b/httpproject/httpclient.c index 40b973a..7b0cc46 100644 --- a/httpproject/httpclient.c +++ b/httpproject/httpclient.c @@ -6,45 +6,240 @@ #include #include +#define TRUE 1 +#define FALSE 0 + +typedef struct InputData{ + char *server_info; + char *uri; + char *data; +}InputData; + +// 매개변수 인자개수 및 http:// 체크 +bool IsInputCorrect(int argc, char *input); + +// 서버 정보, uri, 데이터로 구분하여 파싱 +InputData* ParseInputString(char *argv[], int argc); + +// InputData 메모리 할당해제 +void FreeInputData(InputData *input_data); + +char* HandleGetMethod(qhttpclient_t *http_client, const InputData *input_data); + +char* HandlePostMethod(qhttpclient_t *http_client, const InputData *input_data); + +// POST의 경우 바디 데이터에 json데이터 추가 +json_t* CreateJsonObject(const InputData* input_data); + +// 서버로 부터 받은 데이터 출력 +void PrintReceivedData(char *reiceve_data); + int main(int argc, char *argv[]){ - qhttpclient_t *httpclient = qhttpclient("http://localhost:50003", 0); + if(!IsInputCorrect(argc, argv[1])){ + printf("input is wrong\n"); + return 0; + } + + struct InputData* input_data = ParseInputString(argv, argc); + + if(!input_data){ + printf("input is wrong\n"); + return 0; + } + + qhttpclient_t *http_client = qhttpclient(input_data->server_info, 0); + + if(http_client == NULL){ + printf("qhttpclient fail\n"); + return 0; + } + + if(http_client->open(http_client) == false){ + printf("서버와의 연결이 실패하였습니다.\n"); + return 0; + } - if(httpclient == NULL){ - printf("server is not runninng\n"); - return 0; + char *receive_data = NULL; + + if(argc == 2){ // GET + receive_data = HandleGetMethod(http_client, input_data); + }else{ // POST + receive_data = HandlePostMethod(http_client, input_data); } - if(httpclient->open(httpclient) == false){ - return; + if(receive_data != NULL){ + PrintReceivedData(receive_data); + + free(receive_data); } - printf("server is running\n"); + FreeInputData(input_data); + + http_client->close(http_client); + http_client->free(http_client); + + return 0; +} + +bool IsInputCorrect(int argc,char *input){ + if(argc != 2 && argc != 3){ + return FALSE; + } + + char *ptr = strstr(input, "http://"); + + return (ptr != NULL); +} + +InputData* ParseInputString(char *argv[], int argc){ + struct InputData *input_data = (InputData*)malloc(sizeof(InputData)); + + if(input_data == NULL){ + return NULL; + } + + memset(input_data, 0, sizeof(InputData)); + + int length = 0; + int i = 0; + char *pointer = strstr(argv[1], "http://"); + + pointer = pointer + strlen("http://"); + + // 서버 정보를 저장하기위해 http://이후 처음 만나는 '/'으로 이동 + pointer = strstr(pointer, "/"); + + // '/'가 없다는 것은 uri가 없다는 뜻이므로 에러 처리 + if(pointer == NULL){ + return NULL; + } + + pointer = pointer + 1; + + // 서버 정보는 존재하지만 uri가 존재하지 않는 경우 + if(*pointer == '\0'){ + return NULL; + } - int rescode = 0; + // uri정보가 시작하는 포인터와 문자열의 첫번째 포인터를 빼서 길이를 구한다. + length = pointer - argv[1]; + input_data->server_info = (char*)malloc(length + 1); + + for(i = 0; i < length; i++){ + input_data->server_info[i] = argv[1][i]; + } + input_data->server_info[length] = '\0'; + + // 서버 정보의 뒤의 포인터가 위치하는 부분부터는 uri정보이므로 해당 문자열 저장 + input_data->uri = (char*)malloc(strlen(pointer) + 1); + strcpy(input_data->uri, pointer); + + // POST의 경우 바디데이터에 들어갈 데이터를 구분하여 저장 + if(argc == 3){ + input_data->data = (char*)malloc(strlen(argv[2]) + 1); + strcpy(input_data->data, argv[2]); + } + + return input_data; +} + +void FreeInputData(InputData *input_data){ + if(input_data){ + if(input_data->server_info){ + free(input_data->server_info); + } + + if(input_data->uri){ + free(input_data->uri); + } + + if(input_data->data){ + free(input_data->data); + } + + free(input_data); + } + } + + +char* HandleGetMethod(qhttpclient_t *http_client, const InputData *input_data){ + if(http_client == NULL || input_data == NULL){ + return NULL; + } + + int response_code = 0; size_t contents_length = 0; - char *data = "havana"; - json_t *object = json_object(); - json_t *string = json_string(data); - json_object_set(object, "song", string); - char *buffer = json_dumps(object, JSON_ENCODE_ANY); + void *receive_data = http_client->cmd(http_client, "GET", input_data->uri, + NULL, 0, + &response_code, &contents_length, + NULL, NULL); + + return (char*)receive_data; +} + - void *contents = httpclient->cmd(httpclient, "POST", "info/singer", - (void*)buffer, strlen(buffer) + 1, - &rescode, &contents_length, - NULL, NULL); +char* HandlePostMethod(qhttpclient_t *http_client, const InputData *input_data){ - if(NULL != contents){ - printf("%s\n", (char*)contents); - free(contents); - }else{ - printf("NULL\n"); + if(http_client == NULL || input_data == NULL){ + return NULL; } + void *receive_data = NULL; + off_t receive_length = 0; - httpclient->close(httpclient); - httpclient->free(httpclient); + // HTTP Body에 전달할 json object생성 + json_t *input_object = CreateJsonObject(input_data); - return 0; + // Http Server로 전달하기위해 문자열 타입으로 변환 + char *send_data = json_dumps(input_object, JSON_ENCODE_ANY); + qlisttbl_t *request_headers = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE); + int response_code = 0; + + request_headers->putstr(request_headers, "Content-Type", "application/x-www-form-urlencoded"); + request_headers->putint(request_headers, "Content-Length", strlen(send_data) + 1); + + receive_data = http_client->cmd(http_client, "POST", input_data->uri, + send_data, strlen(send_data) + 1, + &response_code, &receive_length, + request_headers, NULL); + + json_decref(input_object); + + if(send_data != NULL){ + free(send_data); + } + + request_headers->free(request_headers); + + return (char*)receive_data; } + +json_t* CreateJsonObject(const InputData* input_data){ + json_t *input_object = json_object(); + json_t *song_data = json_string(input_data->data); + json_object_set(input_object, "song", song_data); + + return input_object; +} + +void PrintReceivedData(char *receive_data){ + if(receive_data == NULL){ + return; + } + + json_t* receive_object = json_loads(receive_data, JSON_DECODE_ANY, NULL); + + void *iterator = json_object_iter(receive_object); + + printf("{ "); + while(iterator){ + json_t* value = json_object_iter_value(iterator); + printf("%s : %s", json_object_iter_key(iterator), json_string_value(value)); + iterator = json_object_iter_next(receive_object, iterator); + } + printf(" }\n"); + + json_decref(receive_object); +} diff --git a/httpproject/httpserver.c b/httpproject/httpserver.c index e54f04c..84ef879 100644 --- a/httpproject/httpserver.c +++ b/httpproject/httpserver.c @@ -8,110 +8,463 @@ #define TRUE 1 #define FALSE 0 +typedef struct PostStatus{ + int status; + char *buffer; +}PostStatus; + json_t* LoadJsonFile(); +// 클라리언트의 요청을 처리해주는 함수 static int AnswerToConnection(void *cls, struct MHD_Connection *connection, - const char *url, const char *method, - const char *version, const char *upload_data, - size_t *upload_data_size, void **con_cls); + const char *uri, const char *method, + const char *version, const char *upload_data, + size_t *upload_data_size, void **con_cls); + +// Request처리 완료시 할당한 메모리 해제 작업 +static void RequestCompleted(void *cls, struct MHD_Connection *connection, + void **con_cls, enum MHD_RequestTerminationCode toe); + +// 클라이언트에게 데이터 전달 +static int SendData(struct MHD_Connection *connection, const char *data); + +// Uri 파싱하는 함수 +char** ParseUri(int *size, const char *url); + +// 파싱한 Uri 메모리 할당 해제 +void FreeParseResult(char **parse_result, int size); + +// json으로부터 노래정보를 파싱 +char* GetSongInformation(char **parse_result, int size, char *song_name); + +// 하나의 JsonObject에서 요청받은 노래의 가수인지를 확인하고 가수의 이름을 얻어오는 함수 +int GetSingerFromJsonObject(json_t *object, char *song_name); + +// 하나의 JsonObject에서 요청받은 노래인지를 확인하고 가사를 얻어오는 함수 +int GetLyricFromJsonObject(json_t *object, char *song_name); + +// JsonObject로이루어진 JsonArray로부터 key_data 값을 가지는 Json_t 데이터를 반환 +json_t* FindDataFromJsonArray(json_t *array, char *key_data); + +// JsonObject로부터 key_data 값을 가지는 Json_t 데이터를 반환 +json_t* FindDataFromJsonObject(json_t *object, char *key_data); + +// 에러 발생시 출력해줄 문자열 추가 +char* CreateErrorObject(); + +json_t *json_file = NULL; +char *error_string = NULL; int main(int argc, char *argv[]){ if(argc != 2){ printf("인자의 개수는 1개여야합니다.\n"); - return 0; + return 0; } - // InitializeSignal(); - - json_t *json_root = NULL; - json_error_t error; int port_number = atoi(argv[1]); struct MHD_Daemon *daemon = NULL; + + json_file = LoadJsonFile(NULL); - json_root = LoadJsonFile(&error); - if(json_root == NULL){ + if(json_file == NULL){ printf("json file doesn't exists\n"); - return 0; + return 0; } - printf("port_number : %d\n", port_number); + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port_number, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_NOTIFY_COMPLETED, RequestCompleted, + NULL, MHD_OPTION_END); - daemon = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD, port_number, NULL, NULL, - &AnswerToConnection, NULL, MHD_OPTION_END); - if(daemon == NULL){ printf("daemon start fail\n"); return 0; } + error_string = CreateErrorObject(); + printf("Server is running\n"); getchar(); - /* while(!exitstatus) - * { - * sleep(1); - * } */ - MHD_stop_daemon(daemon); - json_decref(json_root); + json_decref(json_file); + + if(error_string != NULL){ + free(error_string); + } + + printf("서버가 종료되었습니다.\n"); - printf("server is exit\n"); + return 0; +} + +json_t* LoadJsonFile(json_error_t *error){ + json_t *json_file = NULL; + + json_file = json_load_file("./songdata.json", 0, error); + + return json_file; } static int AnswerToConnection(void *cls, struct MHD_Connection *connection, - const char *url, const char *method, - const char *version, const char *upload_data, - size_t *upload_data_size, void **con_cls){ + const char *uri, const char *method, + const char *version, const char *upload_data, + size_t *upload_data_size, void **con_cls){ + + struct PostStatus *post = (struct PostStatus*)*con_cls; + + if(0 == strcmp(method, "POST")){ + if(post == NULL){ + post = malloc(sizeof(struct PostStatus)); + memset(post, 0, sizeof(struct PostStatus)); + *con_cls = post; - char *hello_browser = "hello browser"; - int ret = MHD_YES; + return MHD_YES; + } + } + + // Uri를 파싱하여 parse_result 문자열 배열에 차례대로 저장 + int uri_size = 0; + char **parse_result = ParseUri(&uri_size, uri); + + if(parse_result == NULL){ + return MHD_NO; + } - printf("%s %s %s %u\n", method, url, upload_data, *upload_data_size); + char *song_information = NULL; + int result = MHD_NO; if(0 == strcmp(method, "GET")){ - json_t *object = json_object(); - json_t *string = json_string("bye"); - json_object_set(object, "hi", string); - int length = json_object_size(object); + if(uri_size != 0){ + // json파일로부터 노래정보를 문자열형태로 얻어오는 함수 + song_information = GetSongInformation(parse_result, uri_size - 1, parse_result[uri_size - 1]); + + // 유저가 요청한 uri 출력 + printf("%s\n\n", uri); + + + FreeParseResult(parse_result, uri_size); + + if(song_information != NULL){ + result = SendData(connection, song_information); + + return result; + } + } + } + + if(0 == strcmp(method, "POST")){ + if(*upload_data_size != 0){ + // 클라이언트가 Http body로 보낸 데이터를 json_object로 만듬 + json_t *song_data_object = json_loads(upload_data, JSON_DECODE_ANY, NULL); + json_t *song_string = json_object_get(song_data_object, "song"); + + printf("%s\n", uri); + printf("{\n song : %s \n}\n\n", json_string_value(song_string)); - char *buffer = json_dumps(object, JSON_ENCODE_ANY); + if(uri_size != 0){ + song_information = GetSongInformation(parse_result, uri_size, (char*)json_string_value(song_string)); - free(object); - free(string); + FreeParseResult(parse_result, uri_size); - struct MHD_Response *response = MHD_create_response_from_buffer(strlen(buffer) + 1, buffer, MHD_RESPMEM_PERSISTENT); + if(song_information != NULL){ + post->buffer = song_information; + } - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + *upload_data_size = 0; - MHD_destroy_response(response); + return MHD_YES; + } - return ret; + }else if(post->buffer != NULL){ + return SendData(connection, post->buffer); + } } - if(0 == strcmp(method, "POST")){ - if(0 != *upload_data_size) - { - struct MHD_Response *response = MHD_create_response_from_buffer(strlen(hello_browser) + 1, hello_browser, MHD_RESPMEM_PERSISTENT); + // 서버에 저장되지않은 노래의 정보이거나 지원하지않는 메소드일경우 에러메시지 전달 + return SendData(connection, error_string); +} + +static int SendData(struct MHD_Connection *connection, const char *data){ + int result = MHD_NO; + struct MHD_Response *response = NULL; + + response = MHD_create_response_from_buffer(strlen(data) + 1, (void*)data, MHD_RESPMEM_PERSISTENT); + + if(response == NULL){ + return MHD_NO; + } + + result = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + + return result; +} + +static void RequestCompleted(void *cls, struct MHD_Connection *connection, + void **con_cls, enum MHD_RequestTerminationCode toe){ - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + struct PostStatus *post = (struct PostStatus*)*con_cls; - MHD_destroy_response(response); - } + if(NULL == post){ + return; + } - return ret; + if(post->buffer != NULL){ + free(post->buffer); } - return ret; + free(post); + + *con_cls = NULL; } +char** ParseUri(int *size, const char *url){ + int numOfResource = 0; + char **parse_result = NULL; + const char *current_pointer = url; + const char *before_pointer = NULL; + int string_length = 0; + int string_index = 0; + int array_index = 0; + + // '/'의 개수를 세어 uri의 개수를 센다 + while(*current_pointer != '\0'){ + if(*current_pointer == '/'){ + numOfResource++; + } + + current_pointer++; + } -json_t* LoadJsonFile(json_error_t *error){ - json_t *json_root = NULL; + if(numOfResource == 0){ + return NULL; + } + + // 현재 uri가 info/lyrics와 같이 들어오므로 개수 1개를 늘려준다 + numOfResource++; + + parse_result = (char**)malloc(sizeof(char*) * numOfResource); - json_root = json_load_file("./songdata.json", 0, error); + if(parse_result == NULL){ + return NULL; + } + + *size = numOfResource; + + current_pointer = url; + before_pointer = current_pointer; + + while(*current_pointer != '\0'){ + if(*current_pointer == '/'){ + + // '/'를 만나는 경우 before_pointer와 빼서 문자열의길이를 구한다. + string_length = current_pointer - before_pointer + 1; + string_index = 0; + + parse_result[array_index] = (char*)malloc(sizeof(char) * string_length); + + if(parse_result[array_index] == NULL){ + return NULL; + } + + // 현재 포인터와 이전 포인터가 같아지는 지점끼리 문자열 저장 + while(before_pointer != current_pointer){ + parse_result[array_index][string_index++] = *before_pointer; + before_pointer++; + } + parse_result[array_index][string_index] = '\0'; + + array_index++; + before_pointer++; + } + + current_pointer++; + } + + // 마지막 문자열 처리 + string_length = current_pointer - before_pointer + 1; + string_index = 0; + + parse_result[array_index] = (char*)malloc(sizeof(char) * string_length); + + if(parse_result[array_index] == NULL){ + return NULL; + } + + while(before_pointer != current_pointer){ + parse_result[array_index][string_index++] = *before_pointer++; + } + parse_result[array_index][string_index] = '\0'; + + return parse_result; + } + +void FreeParseResult(char **parse_result, int size){ + int i = 0; + + for(i = 0; i < size; i++){ + if(parse_result[i]){ + free(parse_result[i]); + parse_result[i] = NULL; + } + } + + if(parse_result){ + free(parse_result); + parse_result = NULL; + } +} + +// json_object로부터 key_data와 일치하는 json_t 타입 반환 +json_t* FindDataFromJsonObject(json_t *object, char *key_data){ + void *iterator = json_object_iter(object); + const char *key = NULL; + json_t* data_object = NULL; + + while(iterator){ + key = json_object_iter_key(iterator); + + if(0 == strcasecmp(key, key_data)){ + data_object = json_object_iter_value(iterator); + break; + } + + iterator = json_object_iter_next(object, iterator); + } + + return data_object; +} + +// json object로 이루어진 jsonArray로부터 key_data를 가지는 json_object리턴 +json_t* FindDataFromJsonArray(json_t *array, char *key_data){ + size_t size = json_array_size(array); + size_t i = 0; + json_t* data_object = NULL; + + for(i = 0; i < size; i++){ + json_t *json_data = json_array_get(array, i); + + if(json_is_object(json_data)){ + data_object = FindDataFromJsonObject(json_data, key_data); + } + + if(data_object != NULL){ + break; + } + } + + return data_object; +} + +// json_object에서 노래이름을 가지고 있는지 여부 반환 +int GetLyricFromJsonObject(json_t *object, char *song_name){ + json_t *song_string = json_object_get(object, "song"); + + if(!song_string){ + return FALSE; + } + + const char *song_data = json_string_value(song_string); + + return (0 == strcasecmp(song_data, song_name)); +} - return json_root; +// json_object에서 노래이름을 포함하는 가수인지 여부를 반환 +int GetSingerFromJsonObject(json_t *object, char *song_name){ + void *iter = NULL; + json_t *song_array = json_object_get(object, "song"); + size_t size = json_array_size(song_array); + size_t i = 0; + + for(i = 0; i < size; i++){ + json_t *song_string = json_array_get(song_array, i); + + if(0 == strcasecmp(song_name, json_string_value(song_string))){ + return TRUE; + } + } + + return FALSE; +} + +// parse_result의 size크기만큼 uri정보를 가지고 있고, song_name은 찾을 노래이름이다. +char* GetSongInformation(char **parse_result, int size, char *song_name){ + size_t i = 0; + json_t *temp = json_file; + json_t *new_object = NULL; + + // 사용자가 요청한 uri를 차례대로 json file에서 찾는다. + for(i = 0; i < size; i++){ + if(json_is_object(temp)){ + temp = FindDataFromJsonObject(temp, parse_result[i]); + }else if(json_is_array(temp)){ + temp = FindDataFromJsonArray(temp, parse_result[i]); + }else{ + temp = NULL; + } + + if(!temp){ + return NULL; + } + } + + // 그 후 가사인지 가수를 요청했는지를 알아보고 그에따라서 결과값 리턴 + if(0 == strcasecmp(parse_result[size - 1], "lyrics")){ + size_t size = json_array_size(temp); + json_t *lyric = NULL; + + for(i = 0; i < size; i++){ + json_t *object = json_array_get(temp, i); + + if(TRUE == GetLyricFromJsonObject(object, song_name)){ + lyric = json_object_get(object, "lyric"); + break; + } + } + + if(lyric != NULL){ + new_object = json_object(); + json_object_set(new_object, "lyric", lyric); + } + + }else if(0 == strcasecmp(parse_result[size - 1], "singer")){ + size_t size = json_array_size(temp); + json_t *singer = NULL; + + for(i = 0; i < size; i++){ + json_t *object = json_array_get(temp, i); + + if(TRUE == GetSingerFromJsonObject(object, song_name)){ + singer = json_object_get(object, "name"); + break; + } + } + + if(singer != NULL){ + new_object = json_object(); + json_object_set(new_object, "singer", singer); + } + + }else{ + return NULL; + } + + if(new_object == NULL){ + return NULL; + }else{ + return json_dumps(new_object, JSON_ENCODE_ANY); + } +} + +char* CreateErrorObject(){ + json_t *error_string = json_string("error"); + json_t *error_object = json_object(); + + json_object_set(error_object, "Message", error_string); + + return json_dumps(error_object, JSON_ENCODE_ANY); } + diff --git a/httpproject/songdata.json b/httpproject/songdata.json index fc2582e..f6af1ed 100644 --- a/httpproject/songdata.json +++ b/httpproject/songdata.json @@ -1,10 +1,13 @@ { "info" :{ "lyrics" : [ - { "song" : "havana" } + { "song" : "havana", "lyric" : "Havana ooh na-na ay\nHalf of my heart is in Havana ooh-na-na ay ay\nHe took me back to East Atlanta na-na-na\nAll of my heart is in Havana ay\nThere's somethin' 'bout his manners uh huh\nHavana ooh na-na\nHe didn't walk up with that how you doin'?\nWhen he came in the room\nHe said there's a lot of girls I can do with\nBut I can't without you\nI'm doin' forever in a minute\nThat summer night in June\nAnd papa says he got malo in him\nHe got me feelin' like\n\nOoh... I knew it when I met him\nI loved him when I left him\nGot me feelin' like\nOoh... and then I had to tell him\nI had to go oh na-na-na-na-na\n\nHavana ooh na-na ay\nHalf of my heart is in Havana ooh-na-na ay ay\nHe took me back to East Atlanta na-na-na uh huh\nAll of my heart is in Havana ay\nMy heart is in Havana\nHavana ooh na-na\n\nJeffery\nJust graduated fresh on campus mmm\nFresh out East Atlanta with no manners damn\nFresh out East Atlanta\nBump on her bumper like a traffic jam jam\nHey I was quick to pay\nthat girl like Uncle Sam here you go ay\nBack it on me shawty cravin' on me\nGet to diggin' on me on me\nShe waited on me then what?\nShawty cakin' on me got the bacon on me wait up\nThis is history in the makin' on me on me\nPoint blank close range that be\nIf it cost a million that's me that's me\nI was gettin' mula man they feel me\n\nHavana ooh na-na ay ay\nHalf of my heart is in Havana ooh-na-na oh ay ay\nHe took me back to East Atlanta na-na-na oh no\nAll of my heart is in Havana\nMy heart is in Havana\nHavana ooh na-na\n\nOoh na-na oh na-na-na\nTake me back back back like\nOoh na-na oh na-na-na\nTake me back back back like\nOoh na-na oh na-na-na\nTake me back back back like\nOoh na-na oh na-na-na\nTake me back back back\neah ay...\nOoh...\nTake me back to my Havana...\n\nHavana ooh na-na\nHalf of my heart is in Havana ooh-na-na oh yeah\nHe took me back to East Atlanta na-na-na\nAll of my heart is in Havana\nMy heart is in Havana ay\nHavana ooh na-na\n\nUh huh\nOh na-na-na...\nNo no no take me back\nOh na-na-na\nHavana ooh na-na" }, + + { "song" : "sing me to sleep", "lyric" : "Wait a second, let me catch my breath\nRemind me how it feels to hear your voice\nYour lips are movin', I can't hear a thing\nLivin' life as if we had a choice\nAnywhere, anytime\nI would do anything for you\nAnything for you\nYesterday got away\nMelodies stuck inside your head\nA song in every breath\nSing me to sleep now\nSing me to sleep\nWon't you sing me to sleep now?\nSing me to sleep\nRemember me now, time cannot erase\nI can hear your whispers in my mind\nI've become what you cannot embrace\nOur memory will be my lullaby\nSing me to sleep now\nSing me to sleep\nWon't you sing me to sleep now?\nSing me to sleep\nA-anytime\nI would do\nTime away\nYesterday-day\nA-anytime\nI would do\nTime away\nYesterday-day"} ], "singer":[ - {"name" : "Camila Cabello", "song" : ["havana"]} + {"name" : "Camila Cabello", "song" : ["havana"]}, + {"name" : "Alan Walker", "song" : ["sing me to sleep", "faded"]} ] } } -- GitLab