#include <arpa/inet.h>

#include <dirent.h>

#include <errno.h>

#include <fcntl.h>

#include <netdb.h>

#include <netinet/in.h>

#include <pthread.h>

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <unistd.h>

#include <unistd.h>


#include "libhttp.h"

#include "wq.h"


/*

 * Global configuration variables.

 * You need to use these in your implementation of handle_files_request and

 * handle_proxy_request. Their values are set up in main() using the

 * command line arguments (already implemented for you).

 */

wq_t work_queue; // Only used by poolserver

int num_threads; // Only used by poolserver

int server_port; // Default value: 8000

char* server_files_directory;

char* server_proxy_hostname;

int server_proxy_port;


/*

 * Serves the contents the file stored at `path` to the client socket `fd`.

 * It is the caller's reponsibility to ensure that the file stored at `path` exists.

 */



int send_data(int fd,char* data, size_t size)

{

	ssize_t bytes_size;

	while(size>0)

	{

		bytes_size=write(fd,data,size);

		if(bytes_size>=0)

		{

			size-=bytes_size;

			data+=bytes_size;

		}

		else

			return -1;

	}

	return 0;

}

int send_file(int fd, int file)

{

	const int buf_size = 4096;

	char buf[buf_size];

	ssize_t bytes_read=read(file, buf, buf_size);

	int status=0;

	while (bytes_read>0)

       	{

		status=send_data(fd, buf, bytes_read);

		if (status<0) 

			return status;

		bytes_read=read(file, buf, buf_size);

	}

	return status;

}

int send_string(int fd, char* data)

{

	return send_data(fd,data,strlen(data));

}

char* join_string(char *str1, char *str2, size_t *size)

{

	size_t len1 = strlen(str1);

	size_t len2 = strlen(str2);

	char *buffer = (char *) malloc(len1 + len2 + 1);

	memcpy(buffer,str1,len1);

	memcpy(buffer+len1,str2,len2+1);

	if (size != NULL) 

		*size=(len1+len2)*sizeof(char);

	return buffer;

}

int get_file_size(int fd)

{

	struct stat st;

	if (fstat(fd,&st)==-1)

       	{

		return -1;

	}

	return st.st_size;

}

void serve_file(int fd, char* path) {


  /* TODO: PART 2 */

  /* PART 2 BEGIN */


  int file=open(path,O_RDONLY);

  char* content_length=(char*) malloc(sizeof(char)*100);

  snprintf(content_length, 100,"%d",get_file_size(file));

  http_start_response(fd, 200);

  http_send_header(fd, "Content-Type", http_get_mime_type(path));

  http_send_header(fd, "Content-Length", content_length); // TODO: change this line too

  http_end_headers(fd);

  send_file(fd,file);

  close(file);

  /* PART 2 END */


  /* PART 2 END */

}


void serve_directory(int fd, char* path) {

  http_start_response(fd, 200);

  http_send_header(fd, "Content-Type", http_get_mime_type(".html"));

  http_end_headers(fd);


  /* TODO: PART 3 */

  /* PART 3 BEGIN */


  // TODO: Open the directory (Hint: opendir() may be useful here)

  char* index_path = join_string(path, "/index.html", NULL);

  struct stat index_info;

  int result = stat(index_path, &index_info);

  if (result == 0) {


  serve_file(fd, index_path);

  }

  else {

  DIR *directory=opendir(path);

  struct dirent* entry;

  while ((entry = readdir(directory)) != NULL)

  {

		char * file_name_buffer;

		if(strcmp(entry->d_name,"..")!=0)

		{

			int length = strlen("<a href=\"//\"></a><br/>") + strlen(path) + strlen(entry->d_name)*2 + 1;

     	 		file_name_buffer = (char* ) malloc(length * sizeof(char));

      			http_format_href(file_name_buffer, path, entry->d_name);

		}

		else

		{

			int length = strlen("<a href=\"//\"></a><br/>") + strlen("..") + strlen("")*2 + 1;

     			file_name_buffer = (char* ) malloc(length * sizeof(char));

      			http_format_href(file_name_buffer, "..", entry->d_name);

		}

		char* buffer1=(char*) malloc(1);

		buffer1[0]='\0';

		buffer1=join_string(buffer1,file_name_buffer,NULL);

		size_t content_length;

		buffer1=join_string(buffer1,"</center></body></html>",&content_length);

		free(file_name_buffer);

		send_string(fd,buffer1);

		closedir(directory);

		return;

	}

  }

  /**

   * TODO: For each entry in the directory (Hint: look at the usage of readdir() ),

   * send a string containing a properly formatted HTML. (Hint: the http_format_href()

   * function in libhttp.c may be useful here)

   */ 


  /* PART 3 END */

}


/*

 * Reads an HTTP request from client socket (fd), and writes an HTTP response

 * containing:

 *

 *   1) If user requested an existing file, respond with the file

 *   2) If user requested a directory and index.html exists in the directory,

 *      send the index.html file.

 *   3) If user requested a directory and index.html doesn't exist, send a list

 *      of files in the directory with links to each.

 *   4) Send a 404 Not Found response.

 *

 *   Closes the client socket (fd) when finished.

 */

void handle_files_request(int fd) {


  struct http_request* request = http_request_parse(fd);


  if (request == NULL || request->path[0] != '/') {

    http_start_response(fd, 400);

    http_send_header(fd, "Content-Type", "text/html");

    http_end_headers(fd);

    close(fd);

    return;

  }

   


  if (strstr(request->path, "..") != NULL) {

    http_start_response(fd, 403);

    http_send_header(fd, "Content-Type", "text/html");

    http_end_headers(fd);

    close(fd);

    return;

  }


  /* Remove beginning `./` */

  char* path = malloc(2 + strlen(request->path) + 1);

  path[0] = '.';

  path[1] = '/';

  memcpy(path + 2, request->path, strlen(request->path) + 1);


  /*

   * TODO: PART 2 is to serve files. If the file given by `path` exists,

   * call serve_file() on it. Else, serve a 404 Not Found error below.

   * The `stat()` syscall will be useful here.

   *

   * TODO: PART 3 is to serve both files and directories. You will need to

   * determine when to call serve_file() or serve_directory() depending

   * on `path`. Make your edits below here in this function.

   */


  /* PART 2 & 3 BEGIN */

  struct stat info;

  int result=stat(path,&info);

  if(result!=0)

  {

	  if (strcmp(request->path, "/") == 0)

	  {

		char* index_path = malloc(strlen(path) + strlen("index.html") + 1);

		strcpy(index_path, path);

		strcat(index_path, "index.html");

		result = stat(index_path, &info);

		if (result == 0) {

		serve_file(fd, index_path);

	  }

	  else

	  {

		http_start_response(fd, 404);

		http_send_header(fd,"Content-Type", "text/html");

		http_end_headers(fd);

	  }

	  free(index_path);

	  }

	  else

	  {

		http_start_response(fd, 404);

		http_send_header(fd, "Content-Type", "text/html");

		http_end_headers(fd);

	  }

  }

  else

  {

  	if(S_ISDIR(info.st_mode))

	{

		serve_directory(fd,path);

	}

	if(S_ISREG(info.st_mode))

	{

		serve_file(fd,path);

	}

  close(fd);

  return;

  }

  /* PART 2 & 3 END */


  close(fd);

  return;

}


/*

 * Opens a connection to the proxy target (hostname=server_proxy_hostname and

 * port=server_proxy_port) and relays traffic to/from the stream fd and the

 * proxy target_fd. HTTP requests from the client (fd) should be sent to the

 * proxy target (target_fd), and HTTP responses from the proxy target (target_fd)

 * should be sent to the client (fd).

 *

 *   +--------+     +------------+     +--------------+

 *   | client | <-> | httpserver | <-> | proxy target |

 *   +--------+     +------------+     +--------------+

 *

 *   Closes client socket (fd) and proxy target fd (target_fd) when finished.

 */

void* forward_data(void* arg)

{

	int* fds = (int*)arg;

	int fd1 = fds[0];

	int fd2 = fds[1];

	char buf[4096];

	int len;

	while ((len = read(fd1, buf, 4096)) > 0)

       	{

		write(fd2, buf, len);

	}

	close(fd1);

	close(fd2);

	pthread_exit(NULL);

}


void handle_proxy_request(int fd) {


  /*

  * The code below does a DNS lookup of server_proxy_hostname and

  * opens a connection to it. Please do not modify.

  */

  struct sockaddr_in target_address;

  memset(&target_address, 0, sizeof(target_address));

  target_address.sin_family = AF_INET;

  target_address.sin_port = htons(server_proxy_port);


  // Use DNS to resolve the proxy target's IP address

  struct hostent* target_dns_entry = gethostbyname2(server_proxy_hostname, AF_INET);


  // Create an IPv4 TCP socket to communicate with the proxy target.

  int target_fd = socket(PF_INET, SOCK_STREAM, 0);

  if (target_fd == -1) {

    fprintf(stderr, "Failed to create a new socket: error %d: %s\n", errno, strerror(errno));

    close(fd);

    exit(errno);

  }


  if (target_dns_entry == NULL) {

    fprintf(stderr, "Cannot find host: %s\n", server_proxy_hostname);

    close(target_fd);

    close(fd);

    exit(ENXIO);

  }


  char* dns_address = target_dns_entry->h_addr_list[0];


  // Connect to the proxy target.

  memcpy(&target_address.sin_addr, dns_address, sizeof(target_address.sin_addr));

  int connection_status =

      connect(target_fd, (struct sockaddr*)&target_address, sizeof(target_address));


  if (connection_status < 0) {

    /* Dummy request parsing, just to be compliant. */

    http_request_parse(fd);


    http_start_response(fd, 502);

    http_send_header(fd, "Content-Type", "text/html");

    http_end_headers(fd);

    close(target_fd);

    close(fd);

    return;

  }


  /* TODO: PART 4 */

  /* PART 4 BEGIN */

  // Create two threads to handle the forward and backward connections

  pthread_t server_thread, client_thread;

// Create two file descriptors for communication between threads

  int server_fds[2];

  int client_fds[2];

// Initialize the file descriptors

  pipe(server_fds);

  pipe(client_fds);

// Set up the arguments for the threads

  int server_args[2] = {fd, target_fd};

  int client_args[2] = {target_fd, fd};

// Create the forward and backward threads

  pthread_create(&server_thread, NULL, forward_data, (void*)server_args);

  pthread_create(&client_thread, NULL, forward_data, (void*)client_args);

// Wait for the threads to finish

  pthread_join(server_thread, NULL);

  pthread_join(client_thread, NULL);


  /* PART 4 END */

}


#ifdef POOLSERVER

/*

 * All worker threads will run this function until the server shutsdown.

 * Each thread should block until a new request has been received.

 * When the server accepts a new connection, a thread should be dispatched

 * to send a response to the client.

 */

void* handle_clients(void* void_request_handler) {

  void (*request_handler)(int) = (void (*)(int))void_request_handler;

  /* (Valgrind) Detach so thread frees its memory on completion, since we won't

   * be joining on it. */

  pthread_detach(pthread_self());


  /* TODO: PART 7 */

  /* PART 7 BEGIN */


  /* PART 7 END */

}


/*

 * Creates `num_threads` amount of threads. Initializes the work queue.

 */

void init_thread_pool(int num_threads, void (*request_handler)(int)) {


  /* TODO: PART 7 */

  /* PART 7 BEGIN */


  /* PART 7 END */

}

#endif


/*

 * Opens a TCP stream socket on all interfaces with port number PORTNO. Saves

 * the fd number of the server socket in *socket_number. For each accepted

 * connection, calls request_handler with the accepted fd number.

 */

void thread_function(void* arg)

    {

        int client_socket = (int)arg; // 获取客户端套接字号

        handle_files_request(client_socket); // 调用request_handler函数发送响应

        close(client_socket); // 关闭客户端套接字

        return NULL;

    }

    


void serve_forever(int* socket_number, void (*request_handler)(int)) {


  struct sockaddr_in server_address, client_address;

  size_t client_address_length = sizeof(client_address);

  int client_socket_number;


  // Creates a socket for IPv4 and TCP.

  *socket_number = socket(PF_INET, SOCK_STREAM, 0);

  if (*socket_number == -1) {

    perror("Failed to create a new socket");

    exit(errno);

  }


  int socket_option = 1;

  if (setsockopt(*socket_number, SOL_SOCKET, SO_REUSEADDR, &socket_option, sizeof(socket_option)) ==

      -1) {

    perror("Failed to set socket options");

    exit(errno);

  }


  // Setup arguments for bind()

  memset(&server_address, 0, sizeof(server_address));

  server_address.sin_family = AF_INET;

  server_address.sin_addr.s_addr = INADDR_ANY;

  server_address.sin_port = htons(server_port);


  /*

   * TODO: PART 1

   *

   * Given the socket created above, call bind() to give it

   * an address and a port. Then, call listen() with the socket.

   * An appropriate size of the backlog is 1024, though you may

   * play around with this value during performance testing.

   */


  /* PART 1 BEGIN */


  if(bind(*socket_number,(struct sockaddr*)&server_address,sizeof(server_address))==-1)

  {

  	perror("Failed to bind the socket");

	exit(errno);

  }

  if(listen(*socket_number,1024)==-1)

  {

  	perror("Failed to listen to the socket");

	exit(errno);

  }

  /* PART 1 END */

  printf("Listening on port %d...\n", server_port);


#ifdef POOLSERVER

  /*

   * The thread pool is initialized *before* the server

   * begins accepting client connections.

   */

  init_thread_pool(num_threads, request_handler);

#endif


  while (1) {

    client_socket_number = accept(*socket_number, (struct sockaddr*)&client_address,

                                  (socklen_t*)&client_address_length);

    if (client_socket_number < 0) {

      perror("Error accepting socket");

      continue;

    }


    printf("Accepted connection from %s on port %d\n", inet_ntoa(client_address.sin_addr),

           client_address.sin_port);


#ifdef BASICSERVER

    /*

     * This is a single-process, single-threaded HTTP server.

     * When a client connection has been accepted, the main

     * process sends a response to the client. During this

     * time, the server does not listen and accept connections.

     * Only after a response has been sent to the client can

     * the server accept a new connection.

     */

    request_handler(client_socket_number);



#elif FORKSERVER

    /*

     * TODO: PART 5

     *

     * When a client connection has been accepted, a new

     * process is spawned. This child process will send

     * a response to the client. Afterwards, the child

     * process should exit. During this time, the parent

     * process should continue listening and accepting

     * connections.

     */


    /* PART 5 BEGIN */

    

    pid_t child_pid = fork();

    if(child_pid == -1)

    {

	perror("Failed to create a child process");

	exit(errno);

    }

    if(child_pid == 0)

    {

	close(socket_number); 

	request_handler(client_socket_number);

	exit(0);

    }

    else

    {

	close(client_socket_number); 

    }

    /* PART 5 END */


#elif THREADSERVER

    /*

     * TODO: PART 6

     *

     * When a client connection has been accepted, a new

     * thread is created. This thread will send a response

     * to the client. The main thread should continue

     * listening and accepting connections. The main

     * thread will NOT be joining with the new thread.

     */


    /* PART 6 BEGIN */

        pthread_t tid;

	if (pthread_create(&tid, NULL, thread_function, &client_socket_number) != 0)

        {

	        perror("Failed to create a new thread");

    		continue; 

	}

    	if (pthread_detach(tid) != 0)

   	{

   	 	perror("Failed to detach the thread");

    		continue; 

    	}

    /* PART 6 END */在part6中实现多线程服务器。

创建一个新的 pthread，向客户端发送正确的响应。
原线程将继续监听并接受传入连接。它不会"join"新线程。