欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

web服务器

程序员文章站 2022-05-08 23:52:19
...
// myserver_threadpool.h
#ifndef MYSERVER_THREADPOOL_H #define MYSERVER_THREADPOOL_H #include<list> #include<cstdio> #include<exception> #include<pthread.h> #include"locker.h" template<typename T> class myserver_threadpool { public: myserver_threadpool(int thread_number=0,int max_requests=10000); ~myserver_threadpool(); bool append(T* request); private: static void* worker(void* arg); void run(); private: int m_thread_number; int m_max_requests; pthread_t* m_threads; std::list<T* > m_workqueue; locker m_queuelocker; sem m_queuestat; bool m_stop; }; template<typename T> myserver_threadpool<T>::myserver_threadpool(int thread_number,int max_requests):m_thread_number(thread_number),m_max_requests(max_requests),m_stop(false),m_threads(NULL) { if((thread_number<=0)||(max_requests<=0)) { printf("error1\n"); throw std::exception(); } m_threads=new pthread_t[m_thread_number]; if(!m_threads) { printf("error2\n"); throw std::exception(); } for(int i=0;i<thread_number;i++) { printf("create the %dth thread\n",i); if(pthread_create(m_threads+i,NULL,worker,this)!=0) { delete [] m_threads; throw std::exception(); } if(pthread_detach(m_threads[i])) { delete[] m_threads; throw std::exception(); } } } template<typename T> myserver_threadpool<T>::~myserver_threadpool() { delete[] m_threads; m_stop=true; } template<typename T> bool myserver_threadpool<T>::append(T* request) { m_queuelocker.lock(); if(m_workqueue.size() > m_max_requests) { m_queuelocker.unlock(); return false; } m_workqueue.push_back(request); m_queuelocker.unlock(); m_queuestat.post(); printf("running append() successfully\n"); return true; } template<typename T> void* myserver_threadpool<T>::worker(void* arg) { myserver_threadpool* pool=(myserver_threadpool*)arg; printf("exec pool->run()\n"); pool->run(); printf("finish run()\n"); return pool; } template<typename T> void myserver_threadpool<T>::run() { while(!m_stop) { printf("the next running is wait()\n"); m_queuestat.wait(); printf("finish wait()\n"); m_queuelocker.lock(); if(m_workqueue.empty()) { m_queuelocker.unlock(); continue; } T* request = m_workqueue.front(); m_workqueue.pop_front(); m_queuelocker.unlock(); if(!request) { continue; } printf("the next running is process()\n"); request->process(); printf("finish process()\n"); } } #endif




// my_server.cpp

#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<string.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<stdarg.h>
#include<errno.h>
#include"service.h"
#include"myserver_threadpool.h"
#include"service.cpp"

#define MAX_FD 1024
extern void addfd(int epollfd,int fd,bool one_shot);
extern void modfd(int epollfd,int fd,int ev);
void addsig(int sig,void(handler)(int),bool restart=true)
{
struct sigaction sa;
memset(&sa,'\0',sizeof(sa));
sa.sa_handler=handler;
if(restart)
{
sa.sa_flags|=SA_RESTART;
}
sigfillset(&sa.sa_mask);
assert(sigaction(sig,&sa,NULL)!=-1);
}

void show_error(int connfd,const char* info)
{
printf("%s",info);
send(connfd,info,strlen(info),0);
close(connfd);
}

void close_conn(int epollfd,int fd)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
close(fd);
}

int main(int argc,char* argv[])
{
if(argc<=2)
{
printf("usage %s ip_address port_number\n",basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
addsig(SIGPIPE,SIG_IGN);

myserver_threadpool<service>* pool=NULL;
pool=new myserver_threadpool<service>(3);

service* users=new service[MAX_FD];
assert(users);
int user_count=0;

int listenfd=socket(PF_INET,SOCK_STREAM,0);
assert(listenfd>=0);
struct linger tmp={1,0};
setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&tmp,sizeof(tmp));

int ret=0;
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);

ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));
assert(ret>=0);

ret=listen(listenfd,5);
assert(ret>=0);

epoll_event events[5];
int epollfd=epoll_create(5);
assert(epollfd!=-1);
addfd(epollfd,listenfd,false);
service::m_epollfd=epollfd;
while(1)
{
int number=epoll_wait(epollfd,events,5,-1);

for(int i=0;i<number;i++)
{
int sockfd=events[i].data.fd;
if(sockfd==listenfd)
{
struct sockaddr_in client_address;
socklen_t client_addrlength=sizeof(client_address);
int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
if(connfd<0)
{
printf("errno is: %d\n",errno);
continue;
}
printf("There is a new connection\n");
users[connfd].init(connfd,client_address);

}
else if(events[i].events&(EPOLLRDHUP|EPOLLHUP|EPOLLERR))
{
printf("events is EPOLLRDHUP|EPOLLHUP|EPOLLERR\n");
users[sockfd].close_conn();
}
else if(events[i].events&EPOLLIN)
{
printf("events is EPOLLIN\n");
sockfd=events[i].data.fd;
if(sockfd<0)
continue;
if(users[sockfd].sread())
{
pool->append(users+sockfd);
}
else
{
users[sockfd].close_conn();
}
}
else if(events[i].events&EPOLLOUT)
{
printf("events is EPOLLOUT\n");
sockfd=events[i].data.fd;
if(users[sockfd].swrite())
{
//users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete [] users;
delete pool;
return 0;
}

 

// service.h

#ifndef SERVICE_H
#define SERVICE_H

#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<string.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
#include<errno.h>
#include<time.h>
#include"locker.h"
class service
{
public:
static const int READ_BUFFER_SIZE=2048;
static const int WRITE_BUFFER_SIZE=1024;
static const int FILE_BUFFER_SIZE=2048;
enum METHOD {GET=0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH};
enum CHECK_STATE {CHECK_STATE_REQUESTLINE=0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};
enum HTTP_CODE {NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};
enum LINE_STATUS{LINE_OK=0,LINE_BAD,LINE_OPEN};
public:
service(){};
~service(){};

public:
void init(int sockfd,const sockaddr_in& addr);
void close_conn(bool real_close=true);
void process();
bool sread();
bool swrite();


private:
void init();
bool send_http_head(int conn_socket, int status, char *s_status, char *filetype);
HTTP_CODE process_read();
int send_html();
void add_page_error(int status,char* s_status,char* msg);
void add_http_head(int status, char *s_status, char *filetype);
bool process_write(HTTP_CODE ret);
bool add_response(const char* format,...);
void do_url();

bool add_content(const char* content);
bool add_status_line(int status,const char* title);
bool add_headers(int content_length);
bool add_content_length(int content_length);
bool add_blank_line();
bool add_date();


char* get_line(){return read_buf+m_start_line;}
LINE_STATUS parse_line();

HTTP_CODE parse_request_line(char* text);

public:
static int m_epollfd;
static int m_user_count;

private:
int m_sockfd;
sockaddr_in m_address;
int m_read_idx;
int m_checked_idx;
int m_start_line;
int m_write_idx;

CHECK_STATE m_check_state;
char* m_method=new char[100];

char* m_url=new char[100];
char* m_version=new char[100];
char* m_host;
int m_content_length;

char read_buf[READ_BUFFER_SIZE];
char write_buf[WRITE_BUFFER_SIZE];
char file_buf[FILE_BUFFER_SIZE];

char* m_file_address;
struct stat m_file_stat;
};
#endif

 

 

// locker.h

#ifndef LOCKER_H
#define LOCKER_H

#include<exception>
#include<pthread.h>
#include<semaphore.h>
class sem
{
public:
sem()
{
if(sem_init(&m_sem,0,0)!=0)
{
throw std::exception();
}
}
~sem()
{
sem_destroy(&m_sem);
}
bool wait()
{
return sem_wait(&m_sem)==0;
}
bool post()
{
return sem_post(&m_sem)==0;
}
private:
sem_t m_sem;
};
class locker
{
public:
locker()
{
if(pthread_mutex_init(&m_mutex,NULL)!=0)
{
throw std::exception();
}
}
~locker()
{
pthread_mutex_destroy(&m_mutex);
}
bool lock()
{
return pthread_mutex_lock(&m_mutex)==0;
}
bool unlock()
{
return pthread_mutex_unlock(&m_mutex)==0;
}
private:
pthread_mutex_t m_mutex;
};
class cond
{
public:
cond()
{
if(pthread_mutex_init(&m_mutex,NULL)!=0)
{
throw std::exception();
}
if(pthread_cond_init(&m_cond,NULL)!=0)
{
pthread_mutex_destroy(&m_mutex);
throw std::exception();
}
}
~cond()
{
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_cond);
}
bool wait()
{
int ret=0;
pthread_mutex_lock(&m_mutex);
ret=pthread_cond_wait(&m_cond,&m_mutex);
pthread_mutex_unlock(&m_mutex);
return ret==0;
}

bool signal()
{
return pthread_cond_signal(&m_cond)==0;
}
private:
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
};
#endif

 

// my_server.cpp

#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<string.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<stdarg.h>
#include<errno.h>
#include"service.h"
#include"myserver_threadpool.h"
#include"service.cpp"

#define MAX_FD 1024
extern void addfd(int epollfd,int fd,bool one_shot);
extern void modfd(int epollfd,int fd,int ev);
void addsig(int sig,void(handler)(int),bool restart=true)
{
struct sigaction sa;
memset(&sa,'\0',sizeof(sa));
sa.sa_handler=handler;
if(restart)
{
sa.sa_flags|=SA_RESTART;
}
sigfillset(&sa.sa_mask);
assert(sigaction(sig,&sa,NULL)!=-1);
}

void show_error(int connfd,const char* info)
{
printf("%s",info);
send(connfd,info,strlen(info),0);
close(connfd);
}

void close_conn(int epollfd,int fd)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
close(fd);
}

int main(int argc,char* argv[])
{
if(argc<=2)
{
printf("usage %s ip_address port_number\n",basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
addsig(SIGPIPE,SIG_IGN);

myserver_threadpool<service>* pool=NULL;
pool=new myserver_threadpool<service>(3);

service* users=new service[MAX_FD];
assert(users);
int user_count=0;

int listenfd=socket(PF_INET,SOCK_STREAM,0);
assert(listenfd>=0);
struct linger tmp={1,0};
setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&tmp,sizeof(tmp));

int ret=0;
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);

ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));
assert(ret>=0);

ret=listen(listenfd,5);
assert(ret>=0);

epoll_event events[5];
int epollfd=epoll_create(5);
assert(epollfd!=-1);
addfd(epollfd,listenfd,false);
service::m_epollfd=epollfd;
while(1)
{
int number=epoll_wait(epollfd,events,5,-1);

for(int i=0;i<number;i++)
{
int sockfd=events[i].data.fd;
if(sockfd==listenfd)
{
struct sockaddr_in client_address;
socklen_t client_addrlength=sizeof(client_address);
int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
if(connfd<0)
{
printf("errno is: %d\n",errno);
continue;
}
printf("There is a new connection\n");
users[connfd].init(connfd,client_address);

}
else if(events[i].events&(EPOLLRDHUP|EPOLLHUP|EPOLLERR))
{
printf("events is EPOLLRDHUP|EPOLLHUP|EPOLLERR\n");
users[sockfd].close_conn();
}
else if(events[i].events&EPOLLIN)
{
printf("events is EPOLLIN\n");
sockfd=events[i].data.fd;
if(sockfd<0)
continue;
if(users[sockfd].sread())
{
pool->append(users+sockfd);
}
else
{
users[sockfd].close_conn();
}
}
else if(events[i].events&EPOLLOUT)
{
printf("events is EPOLLOUT\n");
sockfd=events[i].data.fd;
if(users[sockfd].swrite())
{
//users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete [] users;
delete pool;
return 0;
}

 

// service.cpp

#include"service.h"

const char* ok_200_title="OK";
const char* error_400_title="Bad Request";
const char* error_400_form="Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title="Forbidden";
const char* error_403_form="You do not have permission to get file from this server.\n";
const char* error_404_title="Not Found";
const char* error_404_form="The requested file was not found on this server.\n";
const char* error_500_title="Internal Error";
const char* error_500_form="There was an unusual problem serving the requested file.\n";
const char* doc_root="/var/www/html";

int setnonblocking(int fd)
{
int old_option=fcntl(fd,F_GETFL);
int new_option=old_option|O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option;
}
void addfd(int epollfd,int fd,bool one_shot)
{
epoll_event event;
event.data.fd=fd;
event.events=EPOLLIN|EPOLLET|EPOLLRDHUP|EPOLLOUT|EPOLLERR;
if(one_shot)
{
event.events|=EPOLLONESHOT;
}
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
setnonblocking(fd);
}

void modfd(int epollfd,int fd,int ev)
{
epoll_event event;;
event.data.fd=fd;
event.events=ev|EPOLLET|EPOLLONESHOT|EPOLLRDHUP;
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
}


void removefd(int epollfd,int fd)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
close(fd);
}


int service::m_user_count=0;
int service::m_epollfd=-1;
void service::close_conn(bool real_close)
{
if(real_close&&(m_sockfd!=-1))
{
removefd(m_epollfd,m_sockfd);
m_sockfd=-1;
m_user_count--;
}
}

void service::init(int sockfd,const sockaddr_in& addr)
{
m_sockfd=sockfd;
m_address=addr;
addfd(m_epollfd,sockfd,true);
m_user_count++;
init();
}

void service::init()
{
memset(read_buf,'\0',READ_BUFFER_SIZE);
memset(write_buf,'\0',WRITE_BUFFER_SIZE);
memset(file_buf,'\0',FILE_BUFFER_SIZE);
m_check_state=CHECK_STATE_REQUESTLINE;

m_content_length=0;
m_host=0;
m_start_line=0;
m_checked_idx=0;
m_read_idx=0;
m_write_idx=0;
}

bool service::sread()
{
if(m_read_idx>=READ_BUFFER_SIZE)
{
return false;
}
int bytes_read=0;
while(true)
{
bytes_read=recv(m_sockfd,read_buf+m_read_idx,READ_BUFFER_SIZE-m_read_idx,0);
printf("read_buf is %s\n",read_buf);
if(bytes_read==-1)
{
if(errno==EAGAIN||errno==EWOULDBLOCK)
{
break;
}
return false;
}
else if(bytes_read==0)
{
return false;
}
m_read_idx+=bytes_read;
}
return true;
}

bool service::swrite()
{
printf("This is write() m_write_idx=%d\n",m_write_idx);
printf("In swrite() write_buf is %s \n",write_buf);
int temp=0;
int bytes_have_send=0;
int bytes_to_send=m_write_idx;
if(bytes_to_send==0)
{
printf("bytes_to_send = 0\n");
modfd(m_epollfd,m_sockfd,EPOLLIN);
init();
return false;
}

while(1)
{
temp=write(m_sockfd,write_buf,bytes_to_send);
if(temp<=-1)
{
if(errno==EAGAIN)
{
modfd(m_epollfd,m_sockfd,EPOLLOUT);
return true;
}
return false;
}
bytes_to_send-=temp;
bytes_have_send+=temp;
if(bytes_to_send==0)
{
init();
modfd(m_epollfd,m_sockfd,EPOLLIN);
return true;
}
}

}

void service::do_url()
{
char *p;
p=strchr(m_url+1, '?');
if(p)
{
*p = 0;
p++;
}
}

void service::add_page_error(int status,char* s_status,char* msg)
{
add_response("<html><head></head><body><h1> %s </h1><hr>Reage Web Server 0.01</body></head>", msg);
add_http_head(status, s_status, "text/html");
}

void service::add_http_head(int status, char *s_status, char *filetype)
{
add_status_line(status, s_status);
add_date();
add_content_length(m_file_stat.st_size);
add_blank_line();

}

bool service::add_status_line(int status,const char* title)
{
return add_response("%s %d %s\r\n","HTTP/1.1,",status,title);
}

bool service::add_content_length(int content_len)
{
return add_response("Content-Length: %d \r\n",content_len);
}

bool service::add_blank_line()
{
return add_response("%s","\r\n");
}

bool service::add_content(const char* content)
{
return add_response("%s",content);
}

bool service::add_date()
{
time_t timep;
time (&timep);
return add_response("Date: %s",ctime(&timep));
}


bool service::add_response(const char* format,...)
{
if(m_write_idx>=WRITE_BUFFER_SIZE)
{
return false;
}
va_list arg_list;
va_start(arg_list,format);
int len=vsnprintf(write_buf+m_write_idx,WRITE_BUFFER_SIZE-1-m_write_idx,format,arg_list);
if(len>=(WRITE_BUFFER_SIZE-1-m_write_idx))
{
return false;
}
m_write_idx+=len;
va_end(arg_list);
return true;
}


int service::send_html()
{

int f;
int tmp;
printf("m_url = %s\n",m_url);
m_url=strchr(m_url,'/');
if(strlen(m_url)==1||!strcasecmp(m_url,"/favicon.ico"))
{
printf("exec strcpy\n");
strcpy(m_url,"index.html");
}
else
{
printf("exec strchr\n");
m_url=m_url+1;
printf("m_url is %s\n",m_url);
}
if(stat(m_url,&m_file_stat)>=0)
{
//add_page_error(404,"Not found", "Not found<br/> Reage does not implement this mothod\n");
printf("file exist\n");

}
else if(!(S_ISREG(m_file_stat.st_mode))||!(S_IRUSR&m_file_stat.st_mode))
{
add_page_error(403 , "Forbidden", "Forbidden<br/> Reage couldn't read the file\n");
return 1;
}
add_http_head( 200, "OK", "text/html" );
f=open(m_url,O_RDONLY);
if(f<0)
{
add_page_error(404, "Not found", "Not found<br/> Reage couldn't read the file\n");
}

tmp=read(f,write_buf+m_write_idx,m_file_stat.st_size);
m_write_idx+=m_file_stat.st_size;
return 1;
}


service::HTTP_CODE service::process_read()
{
if(sscanf(read_buf,"%s %s %s",m_method,m_url,m_version))
{
printf("the sscanf data is %s %s %s\n",m_method,m_url,m_version);
printf("request correct\n");
}
else
printf("request error\n");
if(!strcasecmp(m_method, "get"))
do_url();
return FILE_REQUEST;
}

bool service::process_write(HTTP_CODE ret)
{
if(ret==FILE_REQUEST)
{
if(send_html())
return true;
else
return false;
}
return false;
}

void service::process()
{
HTTP_CODE read_ret=process_read();
printf("process() read_ret=%d\n",read_ret);
if(read_ret==NO_REQUEST)
{
modfd(m_epollfd,m_sockfd,EPOLLIN);
return;
}
bool write_ret=process_write(read_ret);
if(!write_ret)
{
close_conn();
}
modfd(m_epollfd,m_sockfd,EPOLLOUT);
}

 

// service.h

#ifndef SERVICE_H
#define SERVICE_H

#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<string.h>
#include<sys/stat.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
#include<errno.h>
#include<time.h>
#include"locker.h"
class service
{
public:
static const int READ_BUFFER_SIZE=2048;
static const int WRITE_BUFFER_SIZE=1024;
static const int FILE_BUFFER_SIZE=2048;
enum METHOD {GET=0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH};
enum CHECK_STATE {CHECK_STATE_REQUESTLINE=0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};
enum HTTP_CODE {NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};
enum LINE_STATUS{LINE_OK=0,LINE_BAD,LINE_OPEN};
public:
service(){};
~service(){};

public:
void init(int sockfd,const sockaddr_in& addr);
void close_conn(bool real_close=true);
void process();
bool sread();
bool swrite();


private:
void init();
bool send_http_head(int conn_socket, int status, char *s_status, char *filetype);
HTTP_CODE process_read();
int send_html();
void add_page_error(int status,char* s_status,char* msg);
void add_http_head(int status, char *s_status, char *filetype);
bool process_write(HTTP_CODE ret);
bool add_response(const char* format,...);
void do_url();

bool add_content(const char* content);
bool add_status_line(int status,const char* title);
bool add_headers(int content_length);
bool add_content_length(int content_length);
bool add_blank_line();
bool add_date();


char* get_line(){return read_buf+m_start_line;}
LINE_STATUS parse_line();

HTTP_CODE parse_request_line(char* text);

public:
static int m_epollfd;
static int m_user_count;

private:
int m_sockfd;
sockaddr_in m_address;
int m_read_idx;
int m_checked_idx;
int m_start_line;
int m_write_idx;

CHECK_STATE m_check_state;
char* m_method=new char[100];

char* m_url=new char[100];
char* m_version=new char[100];
char* m_host;
int m_content_length;

char read_buf[READ_BUFFER_SIZE];
char write_buf[WRITE_BUFFER_SIZE];
char file_buf[FILE_BUFFER_SIZE];

char* m_file_address;
struct stat m_file_stat;

 

};
#endif