JAVA及びC言語でサーバを立てHTML公開

## 目的
TOMCATのようなソフトを使わずにサーバを立てる実験をします。
JAVAだとクラスに細かい処理が隠されてしまって分かりづらいのでC言語でも書いてみました。
(C言語でもライブラリを使っていて完全には自分で書いていませんが。。。)

## アドレス
以下のコマンドでサーバとなるパソコンのIPアドレスを調べて置きます。
```
hostname -I
```
![图片.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/83c95479-6792-4c00-8bcb-06f368bdc5f2.png)

## 結果
JAVAもCも接続の仕方は同じです。
```
http://192.168.68.245:8080
ユーザー名：admin
パスワード：1234
```
IPadから接続した結果を貼ります。
![default.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/5ddfe782-15de-49f6-8803-100ae20a672d.jpeg)
![default(1).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/f7ec6c3c-113b-4f63-bfe0-b626a8bfe464.jpeg)

## java
```
javac SimpleLoginServer.java # コンパイル
java SimpleLoginServer # 実行
```
```SimpleLoginServer.java
import com.sun.net.httpserver.*;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class SimpleLoginServer {
    public static void main(String[] args) throws IOException {
    
        HttpServer server = HttpServer.create(new InetSocketAddress("0.0.0.0", 8080), 0);

        // ログイン画面
        server.createContext("/", exchange -> {
            String response = "<html><body>" +
                    "<form method='POST' action='/login'>" +
                    "Username: <input name='user'><br>" +
                    "Password: <input name='pass' type='password'><br>" +
                    "<input type='submit' value='Login'>" +
                    "</form></body></html>";
            exchange.sendResponseHeaders(200, response.length());
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());// 文字列（HTML）をバイト列に変換して、ブラウザへ送信
            os.close();
        });

        // ログイン処理
        server.createContext("/login", exchange -> {
            if ("POST".equals(exchange.getRequestMethod())) {
                String body = new String(exchange.getRequestBody().readAllBytes());
                if (body.contains("user=admin") && body.contains("pass=1234")) {
                    String response = "<h1>Login Success!</h1>";
                    exchange.sendResponseHeaders(200, response.length());
                    OutputStream os = exchange.getResponseBody();
                    os.write(response.getBytes());
                    os.close();
                } else {
                    exchange.getResponseHeaders().set("Location", "/");
                    exchange.sendResponseHeaders(302, -1);  // リダイレクト
                }
            }
        });

        server.setExecutor(null); // 接続数が多い場合JAVA標準のやり方で分配
        server.start();
        System.out.println("http://localhost:8080 でサーバー起動中");
    }
}
```


## C言語
```
gcc simple_login_server.c -o simple_login_server # コンパイル
./simple_login_server # 実行
```
```simple_login_server.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080

const char *html_form =
    "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
    "<html><body>"
    "<form method='POST' action='/login'>"
    "Username: <input name='user'><br>"
    "Password: <input type='password' name='pass'><br>"
    "<input type='submit' value='Login'>"
    "</form></body></html>";

const char *html_success =
    "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
    "<h1>Login Success!</h1>";

const char *html_failure =
    "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
    "<h1>Login Failed</h1>";

int main() {
    int server_fd, client_fd;
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);//socklen_t : 符号なし整数型（unsigned int相当）
    char buffer[4096];

    //IPv4(AF_INET) TCP(SOCK_STREAM)でソケットを生成
    //成功すると、ソケットを識別するための整数が返る。失敗=１(WindowsのHANDLEに相当)
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        return 1;
    }

    //setsockopt() : ソケットの動作を細かく設定するための関数
    int opt = 1;//オプションを有効にする
    //server_fd : 設定対象のソケット
    //SOL_SOCKET : ソケット全体に関する一般的な設定グループの名前
    //SO_REUSEADDR : アドレス再利用(開放されたポートをすぐに使えるようにする。
    //               通常は暫く経たないと使えない。使用中ポートの奪取は不可。)
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    addr.sin_family = AF_INET; // IPv4使用
    addr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 自分のパソコンの持っているすべてのIPアドレスでアクセスを受け付ける設定
    addr.sin_port = htons(PORT); // ポート番号を指定

    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind");
        return 1;
    }

    // サーバーソケットを「待ち受け状態」　server_fdはソケットの識別番号
    // 5 は「同時接続最大数」
    listen(server_fd, 5); 
    printf("Listening on http://localhost:%d\n", PORT);

    while (1) {
        // 接続要求が来るまで待つ（停止する）
        // 要求が来たら新しいソケットの識別番号を返す。これを元に通信する
        client_fd = accept(server_fd, (struct sockaddr*)&addr, &addrlen);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }

        //buffer : 届いたデータを一時的にためておく場所
        memset(buffer, 0, sizeof(buffer));//buffer初期化
        read(client_fd, buffer, sizeof(buffer) - 1);//この中でデータが来るまで待つのでバッファが溜まり次第次の処理に進む

        // 簡単な判定（GET or POST） write()はLinuxのAPI
        if (strncmp(buffer, "GET / ", 6) == 0) {
            write(client_fd, html_form, strlen(html_form));
        } else if (strncmp(buffer, "POST /login", 11) == 0) {
            // ブラウザから来るデータ例user=admin&pass=1234
            if (strstr(buffer, "user=admin") && strstr(buffer, "pass=1234")) {
                write(client_fd, html_success, strlen(html_success));
            } else {
                write(client_fd, html_failure, strlen(html_failure));
            }
        } else {
            // 404 Not Found
            const char *not_found = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nNot Found";
            write(client_fd, not_found, strlen(not_found));
        }

        close(client_fd);
    }

    close(server_fd);
    return 0;
}

```