#include "StdAfx.h"
#include "server.h"
#include "timeout.h"
#include "04WebServer.h"
#include "connection.h"
#include "dateutility.h"
#include "intervaltimer.h"

/*!
	サーバー実行開始
*/
CServer::CServer(void)
	: m_settingRoot("04WebServer")
{
	DEBUG_SET_THREAD_CLASS_NAME("CServer");

	//	変数初期化
	m_IDCounter = 0;

	//	設定情報をロード
	if(m_settingRoot.LoadFromXMLFile(GET_APP()->GetAppPath() + "04WebServer.xml"))
		throw CServerException("04WebServer.xmlのロードに失敗しました");

	//	設定環境領域を取得
	m_setting = m_settingRoot.GetAccess("Setting");

	//	スレッド環境領域を取得
	m_threadContext = m_context.GetAccess("Threads");

	//	スレッド環境領域のログを取得
	m_threadContextLog = m_context.GetAccess("ThreadLogs");

	//	サーバ情報環境領域を取得
	m_serverInfo = m_infoRoot.GetAccess("ServerInfo");

	//	サーバ情報の設定
	InitServerInfo();

	//	タイムアウト時間の設定
	CTimeOutMgr::GetGlobalManager()->SetDefaultTimeOutTime(m_setting.GetConfig("Basic::TimeOut",300)*1000);

	//	変数初期化
	m_maxConnect = m_setting.GetConfig("Basic::MaxConnect",50);
	m_statusUpdate = -1;

	//	MimeTypesのロード
	m_mimeTypes.Load(m_setting);

	//	帯域制限有効
	m_bandWidthMgr.Load(m_setting);

	//	ログ初期化
	m_log.Init(this);

	//	待ち受け開始
	StartListen();

	//	スレッド開始
	if(StartThread())
		throw CServerException("スレッドの開始に失敗しました");
	
	m_log.WriteSystemLog("サーバを起動しました");
}

CServer::~CServer(void)
{
	EndThread();
}


/*!
 	サーバ情報の設定
*/
void CServer::InitServerInfo(void)
{
	//	サーバーネーム
	m_serverInfo.SetConfig("ServerName",SERVER_NAME);

	//	バージョン
	m_serverInfo.SetConfig("ServerVersion",SERVER_VER);

	//	シグニチャ
	CString	sig = m_setting.GetConfig("Basic::ServerSignature","%ServerName%/%ServerVersion%");
	sig = m_serverInfo.GetKeySwapString(sig);
	m_serverInfo.SetConfig("ServerSignature",sig);
	
	//	サーバールート
	m_serverInfo.SetConfig("ServerRoot",GET_APP()->GetAppPath());

	//	Settingを自動置き換えに
	m_settingRoot.AddAutoSwap(new CSettingSwap(m_serverInfo));
}


/*!
 	待ち受け開始
*/
int CServer::StartListen(void)
{
	try
	{
		//	リスナ起動
		m_listener.Init(m_setting);
	}
	catch(CFastListenerException err)
	{
		m_log.WriteSystemLog("SystemError : " + err.m_info);
		throw CServerException(err.m_info);
	}

	//	アドレス登録
	CIPAddress localhost = CMySocket::GetLocalhost();
	m_serverInfo.SetConfig("ServerAddress",(CString)localhost);
	m_serverInfo.SetConfig("ServerHostName",localhost.GetHostName());

	//	ポート登録
	m_serverInfo.SetConfig("ServerPort",m_setting.GetConfig("Basic::Port",80));

	return(0);
}

/*!
	スレッド本体
*/
void CServer::ThreadMain()
{
	DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain");

	CIntervalTimer	timer(1000);

	while(!m_break)
	{
		try
		{
			DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::Task1");
			//	接続要求は？
			CMySocket *sock = m_listener.GetNextAccess();
			if(sock != NULL)
			{
				if(m_connections.GetSize()<m_maxConnect)
				{
					CConnection	*connect = NULL;
					try
					{
						connect = new CConnection(this,sock);
						m_connections.Add(connect);
					}
					catch(CConnectionException e)
					{
						//	エラー？
						m_log.WriteSystemLog("SystemError : %s",e.m_info);
						if(connect)
							delete connect;

						TRACE("SystemError : %s",e.m_info);
					}
				}
				else
				{
					//	接続数オーバー
					SendBusy(sock);
					delete sock;
				}
			}
			else
			{
				//	処理が無いので、マターリ
				Sleep(20);
			}

			DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::Task2");
			//	終わった接続の削除
			for(int i=(int)m_connections.GetSize()-1;i>=0;i--)
			{
				if(((CConnection *)m_connections[i])->IsRunning() == 0)
				{
					delete (CConnection *)m_connections[i];
					m_connections.RemoveAt(i);
				}
			}

			DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::Task3");
			//	情報の更新は１秒ごと
			if(timer)
			{
				DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::UpdateInfo1");

				//	現在の接続数
				m_serverInfo.SetConfig("CurrentConnection",(int)m_connections.GetSize());


				DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::UpdateInfo2");

				//	現在までの接続数
				m_serverInfo.SetConfig("AllConnected",m_bandWidthMgr.GetAllCount());

				//	現在までの転送量
				m_serverInfo.SetConfig("AllRecvSize",m_bandWidthMgr.GetAllRecv());
				m_serverInfo.SetConfig("AllSendSize",m_bandWidthMgr.GetAllSend());

				//	過去１時間の接続数
				m_serverInfo.SetConfig("Connected1Hour",m_bandWidthMgr.GetCount1h());

				//	過去１時間の転送量
				m_serverInfo.SetConfig("RecvSize1Hour",m_bandWidthMgr.GetRecv1h());
				m_serverInfo.SetConfig("SendSize1Hour",m_bandWidthMgr.GetSend1h());

				DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::UpdateInfo3");
				//	ステータス更新から１日たったか？
				if(m_statusUpdate != -1 && m_statusUpdate + CTimeSpan(1,0,0,0) < CTime::GetCurrentTime())
				{
					m_statusUpdate = -1;
					GET_APP()->SetServerStatus("サーバは正常に作動中です。");
				}

				DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::CheckSocket");
				//	ソケットは正常か？
				if(m_listener.IsError())
				{
					//	再接続する
					m_listener.Close();
					Sleep(10000);
					StartListen();
				}

				DEBUG_SET_THREAD_FUNCTION_NAME("CServer::ThreadMain::LogOut");
				//	ログの更新
				m_log.UpdateLog();
			}
		}
		catch(CMemoryException *e)
		{
			e->Delete();

			//	メモリ不足のため、サーバ一時停止
			m_listener.Close();
			Sleep(30000);
			StartListen();

			m_log.WriteSystemLog("SystemError : メモリ不足のため、いくつかの接続を強制切断しました");
			SetServerStatus("メモリ不足を検出しました、サーバハードの強化を考慮してください。");
		}

	}

	//	クローズ
	m_listener.Close();

	//	全スレッド停止
	KillAllConnection();

	//	ログの更新
	m_log.WriteSystemLog("サーバを停止しました");
	m_log.UpdateLog();
}

/*!
	全ての接続の停止
*/
void CServer::KillAllConnection()
{
	DEBUG_SET_THREAD_FUNCTION_NAME("CServer::KillAllConnection");

	//	サーバー停止フラグ
	m_serverInfo.SetConfig("StopServer",1);

	for(int i=(int)m_connections.GetSize()-1;i>=0;i--)
	{
		((CConnection *)m_connections[i])->Break();
	}

	for(int i=(int)m_connections.GetSize()-1;i>=0;i--)
	{
		delete (CConnection *)m_connections[i];
		m_connections.RemoveAt(i);
	}
}


/*!
	503を送信
*/
void CServer::SendBusy(CMySocket *socket)
{
	DEBUG_SET_THREAD_FUNCTION_NAME("CServer::SendBusy");

	TIMEOUT_SCOPE();
	SetTimeOut(10000);	//	10Sec

	static char _html[] =	"HTTP/1.0 503 Service Unavailable\r\n"\
							"Server: 04WebServer/0.10\r\n"\
							"Content-Type: text/html\r\n"\
							"Connection: Close\r\n"\
							"\r\n"\
							"<HTML><HEAD><TITLE>503 Service Unavailable</TITLE>"\
							"<meta http-equiv=\"Content-Type\" content=\"text/html;charset=Shift_JIS\"></HEAD>\n"\
							"<BODY><H1>Error : 503 Service Unavailable</H1>"\
							"<PRE>接続数が限界を超えました</PRE>"\
							"<HR><ADDRESS>04WebServer/"SERVER_VER"</ADDRESS>"\
							"</BODY></HTML>";

	m_errorSocket = socket;
	socket->Send(_html,(int)strlen(_html));
	socket->Close();
	m_errorSocket = NULL;

	m_log.WriteSystemLog("SystemError : 接続数をオーバーしたため、503 Service Unavailableを送信しました");
	SetServerStatus("サーバは正常に作動中ですが、最大接続数をオーバーする接続がありました。DoS攻撃などの可能性があります。");

	//	タイムアウトしてたら、残り全て切断
	if(IsTimeOut())
	{
		m_listener.Close();
		Sleep(30000);
		StartListen();

		m_log.WriteSystemLog("SystemError : サーバ負荷が限界を超えたため、いくつかの接続を強制切断しました");
	}
}

/*!
	タイムアウトハンドラ
*/
void CServer::OnTimeOut()
{
	if(m_errorSocket != NULL)
		m_errorSocket->Close();

	CTimeOut::OnTimeOut();
}


/*!
	ID取得
*/
int CServer::GetNewID()
{
	SCOPE_LOCK();

	return(m_IDCounter++);
}

/*!
	サーバステータスの設定
*/
void CServer::SetServerStatus(CString status)
{
	GET_APP()->SetServerStatus(status);
	m_statusUpdate = CTime::GetCurrentTime();
}

//	接続数の取得
int CServer::GetConnectCount()
{
	return(m_serverInfo.GetConfig("CurrentConnection",0));
}

/*!
	Settingの置き換えコールバック
*/
CString CSettingSwap::SwapString(CString key,CString data)
{
	//	DATE?
	if(	data.Find("Date") != -1 || 
		data.Find("Time") != -1 || 
		data.Find("GMT") != -1 || 
		data.Find("Year") != -1 || 
		data.Find("Month") != -1 || 
		data.Find("Day") != -1 || 
		data.Find("DayOfWeek") != -1 || 
		data.Find("MonthString") != -1 || 
		data.Find("DayOfWeekString") != -1 || 
		data.Find("Hour") != -1 || 
		data.Find("Minute") != -1 || 
		data.Find("Second") != -1)
	{
		CWorkspace	m_date;
		CWorkspaceAccess date = m_date.GetAccess("Date");
		date.Clear();

		//	時刻
		CTime	time=CTime::GetCurrentTime();
		date.SetConfig("Date",time.Format("%Y/%m/%d"));
		date.SetConfig("Time",time.Format("%H:%M:%S"));
		date.SetConfig("GMT",CDateUtility::GetGMTString(time));

		date.SetConfig("Year",time.Format("%Y"));
		date.SetConfig("Month",time.Format("%m"));
		date.SetConfig("Day",time.Format("%d"));
		date.SetConfig("DayOfWeek",time.Format("%w"));

		date.SetConfig("MonthString",time.Format("%b"));
		date.SetConfig("DayOfWeekString",time.Format("%a"));

		date.SetConfig("Hour",time.Format("%H"));
		date.SetConfig("Minute",time.Format("%M"));
		date.SetConfig("Second",time.Format("%S"));
		data = date.GetKeySwapString(data,"");
	}

	//	Infoによる置き換え（m_serverInfoのコールバックにm_setting使用厳禁！　デッドロックの可能性あり）
	data = m_serverInfo.GetKeySwapString(data,"");

	return(data);
}

