Javaデモ/Servlet「チャット:SPA(Single Page Application)」:「ファイル入出力、ファイル・ロック、AJAX、タイマー」:「RandomAccessFile、FileChannel、Charset、ByteBuffer」(Ver.5)


開発環境はEclipseです。
Eclipseにおいてサーバー・サイド用Javaプロジェクトを作る場合は「動的Web」でプロジェクトを作る必要が有ります。
「動的Web」プロジェクト作成時には、必ず「web.xmlデプロイメント記述子の生成」チェックボックスをチェックしてして下さい(下記参照)。

Webアプリ向け開発手順
http://www.javaroad.jp/opensource/js_eclipse6.htm

現在、文字エンコードは多数ありますが、「UTF-8、Shift_JIS」の2つがインターネットでのデファクト・スタンダードですが、Windowsの場合はShift_JISとは微妙に違うMS932(CP932)がデファクト・スタンダードになります(なのでWindowsにおいてShift_JISと言う場合は、基本的にMS932(CP932)を意味します)。

デモなのでイロイロ手抜きをしています。

レスポンスの遅延を想定して、タイマー処理はsetInterval()では無く、setTimeout()にする、(レスポンスの遅延を想定して)レスポンスが来てからsetTimeout()を設定する。
つまりsetTimeout()を設定する場所はどこでも良いと言う訳では無い。

サーバー・サイドの場合、多数のユーザーが同時にアクセスした場合を考慮する必要があります。
具体的にはサーバー・サイドの場合は、ファイル出力時にファイルをロックしないとデータ破壊される危険性があります。
また ファイルの書き換え(ファイル入力してファイル出力する)は、ファイル入出力を一体としてロックしないとデータ破壊される危険性があります、つまり ファイル入力とファイル出力でファイル・ロックを2つに分けるとファイル・データが破壊される危険性があります。
下記デモでもファイル入力とファイル出力でファイル・ロックは1つだけだと言うことに注意して下さい。

なお (データの削除などで)ファイル・サイズが前より小さくなる場合は「rewind(~)、ftruncate(~,0)」で一旦ファイル・サイズをゼロにする必要があります。


【example\MyServlet.java】

package example;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MyServlet
 */
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {

  private static final long serialVersionUID = 1;

  int iDebugSW = 0;
  String sEncode = "UTF-8";
  String sASC_LF = String.valueOf((char) 0x0A); // Line Feed.
  String sASC_CR = String.valueOf((char) 0x0D); // Carriage Return.

  String sIndention = sASC_CR;
  String sUE_Indention; // = URLEncoder.encode(sIndention,sEncode);
  String sReg_Indention = "(\\x0D\\x0A|\\x0D|\\x0A)";
  // このデモでは改行コード(sIndention)は「x0D」しか使ってないですが、
  // OSによって改行コードが違うので、正規表現で文字列を分割する。

  String sDelimiter = "<>"; // "\t"; //
  // $Delimiterは実際は"\t"タブ文字などが推奨されますが、
  // デバッグ用表示には"<>"の方が分かりやすいでしょう。
  String sUE_Delimiter; // = URLEncoder.encode(sDelimiter,sEncode);

  String sFDirectory = "C:/pleiades/";
  String sFName_Article = "Article.txt";
  String sFName_Template = "Template.html";
  String sFPathName_Article = sFDirectory + sFName_Article;
  String sFPathName_Template = sFDirectory + sFName_Template;
  String sFFirst_Print = "@Print_";
  String sFExt_Print = ".txt";
  Charset oCharset = Charset.forName(sEncode);

  int iArticleMax = 100;

  boolean Article_Add; // = false; // true; //
  // int iLastSerial; // = -1;
  String sPT_Name;
  String sPT_Submit;
  String sPT_LineMarker;
  String sPT_Comment;

  /**
   * @see HttpServlet#HttpServlet()
   */
  public MyServlet() {
    super();
    // TODO Auto-generated constructor stub
  }

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
   *      response)
   */
  protected void doGet(
      HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
    sUE_Delimiter = URLEncoder.encode(sDelimiter, sEncode);
    sUE_Indention = URLEncoder.encode(sIndention, sEncode);
    Article_Add = false; // true; //

    request.setCharacterEncoding(sEncode);

    sPT_Name = request.getParameter("Name");
    if (sPT_Name == null)
      sPT_Name = "";

    sPT_Submit = request.getParameter("Submit");
    if (sPT_Submit == null)
      sPT_Submit = "";

    sPT_LineMarker = request.getParameter("LineMarker");
    if (sPT_LineMarker == null)
      sPT_LineMarker = "";

    sPT_Comment = request.getParameter("Comment");
    if (sPT_Comment == null)
      sPT_Comment = "";

    System.out.printf("sPT_Name.compareTo(''): %d, %n", sPT_Name.compareTo(""));
    System.out.printf(
        "sPT_Comment.compareTo(''): %d, %n",
        sPT_Comment.compareTo(""));
    if (0 < sPT_Name.compareTo("") & 0 < sPT_Comment.compareTo("")) {
      System.out.println(
          "if ( 0<sPT_Name.compareTo('') & 0<sPT_Comment.compareTo(''))");
      Article_Add = true; // false; //
    }

    String sResponse = "";
    if (!sPT_Submit.equals("AJAX")) {
      sResponse = File_Template();
    }
    if (sPT_Submit.equals("AJAX")) {
      System.out.println("if (sPT_Submit.equals('AJAX'))");
      sResponse = File_Article(sPT_LineMarker);
      System.out.printf("sResponse: %s, %n", sResponse);
    }

    response.setContentType("text/html; charset = " + sEncode);
    PrintWriter writer = response.getWriter();
    writer.print(sResponse);
  }

  /**
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
   *      response)
   */
  protected void doPost(
      HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
    doGet(request, response);
  }

  String File_Template() {
    String line = null;
    String sTemplate = "";
    try ( // これはtry-with-resources文なので、
        // 自動クローズがサポートされるから、明示的なクローズは必要ない。
        BufferedReader oReader = new BufferedReader(
            new InputStreamReader(new FileInputStream(sFPathName_Template), sEncode));
    //
    ) {
      line = null;
      while ((line = oReader.readLine()) != null) {
        // dlsResponse.add(line);
        line = line //
            .replace("<&$DebugSW>", String.valueOf(iDebugSW)) //
            .replace("<&$UE_Delimiter>", sUE_Delimiter) //
            .replace("<&$UE_Indention>", sUE_Indention) //
            .replace("<&$ArticleMax>", String.valueOf(iArticleMax)); //
        sTemplate += line + sIndention;
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    System.out.println(sFPathName_Template);

    return sTemplate;
  }

  String File_Article(String sMarker) {
    LocalDateTime oTimeStamp = LocalDateTime.now();
    DateTimeFormatter oTDFormatter = DateTimeFormatter.ofPattern(
        "yyyy/MM/dd HH:mm:ss");
    List<String> dlsArticle = new ArrayList<String>();
    String sBuffer, sNewBuffer;
    sBuffer = "";
    sNewBuffer = "";
    String sSummary = "";
    try ( // これはtry-with-resources文なので、
        // 自動クローズがサポートされるから、明示的なクローズは必要ない。
        RandomAccessFile oRW_Article = new RandomAccessFile(
            sFPathName_Article,
            "rw");
        // 読み書き可能な File
        FileChannel oRWC_Article = oRW_Article.getChannel(); //
    // 読み書き可能な FileChannel
    //
    ) {
      int serial = -1;
      String line = null;

      oRWC_Article.lock();
      ByteBuffer oByteBuffer;

      oByteBuffer = ByteBuffer.allocate((int) oRWC_Article.size());
      // oRWChannel.size()が約2GB(2,147,483,647Byte)を超えたらダメ。

      oRWC_Article.read(oByteBuffer);
      oByteBuffer.rewind();
      sBuffer = oCharset.decode(oByteBuffer).toString();

      if (!(null == sBuffer || "".equals(sBuffer))) {
        dlsArticle = new ArrayList(Arrays.asList(sBuffer.split(sReg_Indention)));
      }

      if (0 < dlsArticle.size()) {
        String[] col = dlsArticle.get(0).split(sDelimiter);
        serial = Integer.parseInt(col[0]);
      }

      if (Article_Add) {
        line = oTimeStamp.format(oTDFormatter) +
            sDelimiter +
            sPT_Name +
            sDelimiter +
            sPT_Comment;
        if (serial < 0) {
          serial = 0;
        } else {
          serial++;
          serial = serial & 0xFFFFFFFF;
        }
        line = serial + sDelimiter + line;
        dlsArticle.add(0, line);
      }
      // iLastSerial = serial;

      boolean summary = true; // false; //
      System.out.printf("sMarker: %s, %n", sMarker);
      int iMarker = "".equals(sMarker) ? -1 : Integer.parseInt(sMarker);
      System.out.printf("iMarker: %d, %n", iMarker);
      for (int i = 0; i < Math.min(dlsArticle.size(), iArticleMax); i++) {
        line = dlsArticle.get(i);
        System.out.printf("line: %s, %n", line);
        String[] col = line.split(sDelimiter);
        System.out.printf("col[0]: %s, %n", col[0]);
        serial = Integer.parseInt(col[0]);
        System.out.printf("serial: %s, %n", serial);
        if (iMarker == serial) {
          System.out.println("if(iMarker==serial)");
          summary = false; // true; //
        }
        if (summary) {
          sSummary += line + sIndention;
        }
        line = (i != 0 ? sIndention : "") + line;
        sNewBuffer += line;
      }
      System.out.printf("sSummary: %s, %n", sSummary);

      if (Article_Add) {
        oByteBuffer = oCharset.encode(sNewBuffer);
        oByteBuffer.rewind();

        oRWC_Article.truncate(0);
        // 一旦 ファイル・サイズをゼロにしている。

        oRWC_Article.write(oByteBuffer);
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    return sSummary;
  }
}


【\pleiades\Template.html】※ファイルの設置場所に注意して下さい(pleiades内)。

<!DOCTYPE html>
<html>
<head>
  <meta charset='UTF-8'>
</head>
<style>
  p,
  section {
    border: thin solid #000;
    min-height: 1.5em;
  }

  #idDebug,
  #idDebugControl {
    display: none;
  }

</style>
<body>
  <form action="" method="POST">
    <input type="text" id="idName" name="Name" value="" placeholder="Name"><br>
    <input type="text" id="idComment" name="Comment" value="" placeholder="Comment">
    <button type="button" name="Submit" onclick="sSubmit='Article_Add';">Submit</button>
  </form>
  <main id="idMain">
    <div id="idDebugControl">
      <button type="button" onclick="AJAXComm();">AJAXComm</button><br>
    </div>
    <section id="idDebug">
    </section>
    <section id="idMessage">
    </section>
    <section id="idArticle">
    </section>
  </main>
</body>
<script>
  nDebugSW = Number("<&$DebugSW>");
  sIndention = decodeURI("<&$UE_Indention>");
  sDelimiter = decodeURI("<&$UE_Delimiter>");
  nArticleMax = Number("<&$ArticleMax>");
  nTimeout = 500;
  sSubmit = "";
  LineMarker = "";
  wDebug = document.getElementById("idDebug");
  wDebugControl = document.getElementById("idDebugControl");
  wMessage = document.getElementById("idMessage");
  wName = document.getElementById("idName");
  wComment = document.getElementById("idComment");
  wArticle = document.getElementById("idArticle");
  ConsoleOut(`nDebugSW: ${nDebugSW}`);
  ConsoleOut(`sDelimiter: ${sDelimiter}`);
  if (nDebugSW) {
    wDebug.style.display = "block";
    wDebugControl.style.display = "block";
  }

  function AJAXComm() {
    ConsoleOut();
    ConsoleOut("function AJAXComm()");
    var sAJAX_Charset = "UTF-8";
    var sAJAX_Server = "";
    var sQueryParameter = `Submit=AJAX&LineMarker=${LineMarker}&`;
    sQueryParameter += `Name=${encodeURI(wName.value)}&`;
    ConsoleOut(`sSubmit: ${sSubmit}, `);
    var err = false; // true; //
    if (sSubmit == 'Article_Add') {
      ConsoleOut("if (sSubmit == 'Article_Add')");
      sSubmit = "";
      ConsoleOut(`wName.value: ${wName.value}, `);
      ConsoleOut(`wComment.value: ${wComment.value}, `);
      wMessage.innerText = "";
      if (!wName.value) {
        err = true; // false; //
        wMessage.innerText += "★Nameが入力されていません\n";
      }
      if (!wComment.value) {
        err = true; // false; //
        wMessage.innerText += "★Commentが入力されていません\n";
      }
      sQueryParameter += `Comment=${encodeURI(wComment.value)}&`;
      wComment.value = "";
    }
    ConsoleOut(`sQueryParameter: ${sQueryParameter}, `);
    var sAJAX_Parameter = "";
    var oAJAX = new XMLHttpRequest();
    oAJAX.onload = function () {
      ConsoleOut();
      ConsoleOut("oAJAX.onload");
      if (this.readyState === 4 && this.status === 200) {
        ConsoleOut("this.readyState: " + this.readyState);
        ConsoleOut("this.status: " + this.status);
        var res = oAJAX.responseText;
        ConsoleOut(`res: ${res}, `);
        wDebug.innerText = res;
        if (res) {
          var a1sArticle = res.split(sIndention);
          ConsoleOut("a1sArticle");
          ConsoleOut(a1sArticle);
          var col = null;
          for (var i = a1sArticle.length - 1; 0 <= i; i--) {
            var line = a1sArticle[i];
            if (" " <= line) {
              var wP = document.createElement('p');
              wP.innerText = line;
              wArticle.prepend(wP);
              col = line.split(sDelimiter);
            }
          }
          /*
          var wFirst = wArticle.firstElementChild;
          if (wFirst) {
            col = wFirst.innerText.split(sDelimiter);
          }
          */
          if (col) {
            LineMarker = Number(col[0]);
          }
          ConsoleOut(`LineMarker: ${LineMarker}, `);
          while (nArticleMax < wArticle.childElementCount) {
            wArticle.lastElementChild.remove();
          }
        }
      }
      if (!nDebugSW) {
        setTimeout(AJAXComm(), nTimeout);
      }
    }
    // oAJAX.overrideMimeType('text/plain; charset=' + sAJAX_Charset);
    oAJAX.open('POST', sAJAX_Server);
    oAJAX.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    oAJAX.send(sQueryParameter);
    ConsoleOut(`oAJAX.send(sQueryParameter)`);
    ConsoleOut(`sQueryParameter: ${sQueryParameter}, `);
  }

  if (!nDebugSW) {
    setTimeout(AJAXComm(), nTimeout);
  }

  function ConsoleOut(msg) {
    if (nDebugSW) {
      msg = msg ? msg : "";
      console.log(msg);
    }
  }
</script>
</html>

コメント

このブログの人気の投稿