Жопа кота

10 заметок с тегом

java

Создание HTTP сервера java

Java имеет огромную экосистему HTTP-серверов и фреймворков, которые находятся на вершине.

Spring — это популярная платформа, которая позволяет программировать с использованием ее API и легко настраивать используемый базовый контейнер сервлетов (Tomcat, Jetty, Undertow).

Другие платформы, такие как Vertx и Play! построены на основе Netty — низкоуровневой асинхронной сетевой среды, управляемой событиями.

Существует также среда Java EE, включающая серверы приложений JBoss, Wildfly и Weblogic.

Когда вы создаете RESTFul-сервис с помощью одной из этих платформ, вы работаете с API и не слишком задумываетесь о том, что происходит под капотом.

В этом сообщении блога мы рассмотрим, как можно создать функциональный API поверх базового HTTP-сервера, просто используя сокеты в Java.

В частности, мы будем:

  1. использовать Sockets API для обработки HTTP-запросов
  2. реализовать очень простой пример декодирования HTTP-запроса GET
  3. предоставить функциональный API, позволяющий конечному пользователю определять маршруты и встраивать сервер в свое приложение.

Надеюсь, это поможет вам лучше понять, что может происходить за кулисами.

Цель здесь не в том, чтобы реализовать полную спецификацию HTTP-сервера с полноценным API поверх него — я даже не уверен, что Socket API достаточно низкого уровня для реализации полной спецификации, например. мы просто рассмотрим запрос GET в качестве учебного упражнения.

Создаем API:

Если у нас есть приложение Java Gradle с файлом build.gradle.kts, мы можем просто включить зависимость от нового серверного проекта, который мы собираемся создать, под названием «functional-server-library».

implementation(project(":functional-server-library"))

Наше основное приложение может затем использовать эту библиотеку, поскольку она предоставляет класс сервера.

Класс сервера будет:

  • возьмите номер порта, на котором он должен прослушивать входящие запросы
  • позволяет добавлять маршруты с помощью метода addRoute
  • начать прослушивать запросы при вызове метода start

Поэтому конечное приложение будет выглядеть примерно так:

public class App {
    public static void main(String[] args) throws IOException {
        Server myServer = new Server(8080);
        myServer.addRoute(GET, "/testOne",
                (req) -> new HttpResponse.Builder()
                        .setStatusCode(200)
                        .addHeader("Content-Type", "text/html")
                        .setEntity("<HTML> <P> Hello There... </P> </HTML>")
                        .build());
        myServer.start();
    }
}

HTTP-протокол

Прежде чем мы углубимся, давайте на секунду вспомним формат сообщений протокола HTTP.

Формат HTTP-запроса является текстовым и может быть разбит на следующие части:

Первая строка состоит из метода HTTP, URI и версии протокола:

Далее следуют:

  • несколько строк заголовков
  • 1 пустая строка
  • необязательное тело запроса для запросов POST/PUT.

Например.

Сообщение запроса HTTP GET:

GET /testOne HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/7.84.0
Accept: */*

Сообщение запроса HTTP POST:

POST /testOne HTTP/1.1
Host: localhost:8080
User-Agent: insomnia/2022.6.0
Content-Type: text/plain
Accept: */*
Content-Length: 11

hello there

HTTP-ответ очень похож:

Первая строка запроса

Общий:

STATUS LINE
HEADER
HEADER
...

BODY

Например.

HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Connection: Keep-Alive

Hello there...

Примечание о версиях HTTP:

Одной из функций, представленных в HTTP 1.1, были постоянные соединения, которые позволяют одному соединению обрабатывать несколько HTTP-запросов. Это более эффективно, поскольку устраняет необходимость в многократном подтверждении TCP.

Эту функцию можно включить, установив для соединения HTTP-заголовка значение Keep-Alive.

Ниже мы собираемся пропустить эту функциональность (а также многие другие части, относящиеся к спецификации HTTP).

Сервер можно разбить на следующие события:

  1. Сервер запускается и прослушивает соединения на определенном порту.
  2. При получении соединения используйте HttpHandler для обработки соединения (передаются входные и выходные потоки).
  3. Декодирование сообщения с помощью HttpDecoder.
  4. Маршрут сообщения для исправления конечной точки.
  5. Запись. ответ с использованием HttpWriter

Разобравшись с этим, приступим к строительству!

public class Server {

    private final Map<String, RequestRunner> routes;
    private final ServerSocket socket;
    private final Executor threadPool;
    private HttpHandler handler;

    public Server(int port) throws IOException {
        routes = new HashMap<>();
        threadPool = Executors.newFixedThreadPool(100);
        socket = new ServerSocket(port);
    }

Созданный threadPool жестко запрограммирован на уровне 100, поэтому это ограничит количество запросов, которые можно будет обрабатывать одновременно (я произвольно выбрал 100, в идеале это должно быть настроено конечным пользователем).

Добавление маршрутов:

Давайте добавим в приложение метод для добавления маршрутов/конечных точек. Для этого мы сохраним маршрут на карте Map..

public void addRoute(HttpMethod opCode, String route, RequestRunner runner) {
    routes.put(opCode.name().concat(route), runner);
}

Ключ карты будет представлять собой комбинацию кода операции запроса (в нашем случае GET) и URI.

RequestRunner — это интерфейс Java, который имеет только один метод, который принимает HttpResponse в качестве аргумента и возвращает объект HttpRequest.

Это позволяет ему быть функциональным интерфейсом, который мы можем предоставить с помощью Lambda.

Например.

public interface RequestRunner {
    HttpResponse run(HttpRequest request);
}

HttpMethod — это простое перечисление:

public enum HttpMethod {
    GET,
    PUT,
    POST,
    PATCH
}

Запускаем сервер:

Теперь мы добавим метод start, который будет ждать входящие запросы и обрабатывать их:

public void start() throws IOException {
    handler = new HttpHandler(routes);

    while (true) {
        Socket clientConnection = socket.accept();
        handleConnection(clientConnection);
    }
}

Socket.accept() блокируется, поэтому handleConnection будет вызываться только тогда, когда клиент подключается к определенному порту.

Мы будем использовать объект HttpHandler, созданный для обработки соединения.

private void handleConnection(Socket clientConnection) {
    try {
        handler.handleConnection(clientConnection.getInputStream(), clientConnection.getOutputStream());
    } catch (IOException ignored) {
    }
}

Однако мы не хотим, чтобы этот 1 запрос блокировал выполнение всех остальных запросов, поэтому мы обернем эту функциональность в Runnable.

Таким образом, жизненный цикл запроса/ответа каждого запроса будет обрабатываться одним потоком (синхронным сервером).

Если бы мы хотели иметь возможность обрабатывать один запрос в нескольких потоках, нам нужно было бы использовать библиотеку нижнего уровня Java NIO (что означает новый ввод-вывод).

Таким образом, вышеизложенное становится:

/*
 * Записывайте каждый жизненный цикл запроса/ответа в потоке,
 * выполняемом в пуле потоков.
 */
private void handleConnection(Socket clientConnection) {
    Runnable httpRequestRunner = () -> {
        try {
            handler.handleConnection(clientConnection.getInputStream(), clientConnection.getOutputStream());
        } catch (IOException ignored) {
        }
    };
    threadPool.execute(httpRequestRunner);
}

2. HttpHandler

  • Декодировать HTTP-запрос
  • Направьте запрос к правильному RequestRunner
  • Записать ответ в выходной поток
/**
 * Обработка жизненного цикла ответа на HTTP-запрос.
 */
public class HttpHandler {

    private final Map<String, RequestRunner> routes;

    public HttpHandler(final Map<String, RequestRunner> routes) {
        this.routes = routes;
    }

Обработка запроса:

public void handleConnection(final InputStream inputStream, final OutputStream outputStream) throws IOException {
    final BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));

    Optional<HttpRequest> request = HttpDecoder.decode(inputStream);
    request.ifPresentOrElse((r) -> handleRequest(r, bufferedWriter), () -> handleInvalidRequest(bufferedWriter));

    bufferedWriter.close();
    inputStream.close();
}

Мы украшаем наш OutputStream BufferedWriter, который позволяет нам записывать текст в поток вывода символов.

Шаги:

  1. Декодируйте сообщение в объект HttpRequest.
  2. Если он присутствует, обработайте
  3. Else, обработайте недопустимый запрос.
  4. Закройте выходные и входные потоки.

Сценарий недопустимого запроса:

Создайте объект HTTP-ответа с кодом состояния 400 и сообщением «Неверный запрос».

private void handleInvalidRequest(final BufferedWriter bufferedWriter) {
    HttpResponse notFoundResponse = new HttpResponse.Builder().setStatusCode(400).setEntity("Bad Request...").build();
    ResponseWriter.writeResponse(bufferedWriter, notFoundResponse);
}

Действительный сценарий запроса:

Получаем ключ маршрута из объекта HttpRequest (uri path), если он есть, извлекаем RequestRunner из Карты маршрутов, выполняем и записываем ответ.

В противном случае мы создаем не найденный ответ с кодом состояния 404.

private void handleRequest(final HttpRequest request, final BufferedWriter bufferedWriter) {
    final String routeKey = request.getHttpMethod().name().concat(request.getUri().getRawPath());

    if (routes.containsKey(routeKey)) {
        ResponseWriter.writeResponse(bufferedWriter, routes.get(routeKey).run(request));
    } else {
        // Not found
        ResponseWriter.writeResponse(bufferedWriter, new HttpResponse.Builder().setStatusCode(404).setEntity("Route Not Found...").build());
    }
}

Читаем сообщение:

Если во входном потоке нет данных, мы возвращаем пустой необязательный параметр, в противном случае данные считываются в созданный буфер char[].

Затем объект Scanner используется для чтения буфера построчно и добавления каждого из них в возвращаемый список массивов.

Если возникает какое-либо исключение, возвращается пустой необязательный параметр.

private static Optional<List<String>> readMessage(final InputStream inputStream) {
    try {
        if (!(inputStream.available() > 0)) {
            return Optional.empty();
        }

        final char[] inBuffer = new char[inputStream.available()];
        final InputStreamReader inReader = new InputStreamReader(inputStream);
        final int read = inReader.read(inBuffer);

        List<String> message = new ArrayList<>();

        try (Scanner sc = new Scanner(new String(inBuffer))) {
            while (sc.hasNextLine()) {
                String line = sc.nextLine();
                message.add(line);
            }
        }

        return Optional.of(message);
    } catch (Exception ignored) {
        return Optional.empty();
    }
}

Создание HTTP-запроса:

Этот метод выполняет простой анализ, чтобы определить, является ли сообщение действительным HTTP-запросом.

Если сообщение пустое или первая строка не содержит трех слов, возвращается пустой необязательный параметр.

Первая строка проверяется:

  1. Использование Enum HttpMethod для извлечения метода HTTP для первого слова
  2. Использование класса java.net.URI для проверки компонента URI.
  3. Сравнение третьего слова (Протокол) с «HTTP/1.1»

Если ни одно из этих условий не выполнено (Исключения обнаружены), возвращается пустой необязательный параметр.

Окончательный анализ заголовков запроса (помните, что мы имеем дело только со сценарием запроса GET без тела) выполняется в методе addRequestHeaders.

private static Optional<HttpRequest> buildRequest(List<String> message) {
    if (message.isEmpty()) {
        return Optional.empty();
    }

    String firstLine = message.get(0);
    String[] httpInfo = firstLine.split(" ");

    if (httpInfo.length != 3) {
        return Optional.empty();
    }

    String protocolVersion = httpInfo[2];
    if (!protocolVersion.equals("HTTP/1.1")) {
        return Optional.empty();
    }

    try {
        Builder requestBuilder = new Builder();
        requestBuilder.setHttpMethod(HttpMethod.valueOf(httpInfo[0]));
        requestBuilder.setUri(new URI(httpInfo[1]));
        return Optional.of(addRequestHeaders(message, requestBuilder));
    } catch (URISyntaxException | IllegalArgumentException e) {
        return Optional.empty();
    }
}
public enum HttpMethod {
    GET,
    PUT,
    POST,
    PATCH
}

Разбор заголовков запроса:

Мы пропускаем первую строку сообщения, как уже читали выше, для каждой оставшейся строки мы проверяем наличие символа двоеточия «:» и просто читаем то, что находится до, как имя заголовка, а то, что следует после, как значение заголовка.

В конце метода мы добавляем новый объект Map>, созданный как заголовки запроса с использованием переданного объекта Builder.

private static HttpRequest addRequestHeaders(final List<String> message, final Builder builder) {
    final Map<String, List<String>> requestHeaders = new HashMap<>();

    if (message.size() > 1) {
        for (int i = 1; i < message.size(); i++) {
            String header = message.get(i);
            int colonIndex = header.indexOf(':');

            if (! (colonIndex > 0 && header.length() > colonIndex + 1)) {
                break;
            }

            String headerName = header.substring(0, colonIndex);
            String headerValue = header.substring(colonIndex + 1);

            requestHeaders.compute(headerName, (key, values) -> {
                if (values != null) {
                    values.add(headerValue);
                } else {
                    values = new ArrayList<>();
                }
                return values;
            });
        }
    }

    builder.setRequestHeaders(requestHeaders);
    return builder.build();
}

теперь мы декодировали наш входной поток в объект HttpRequest.

  1. Писатель ответов (ResponseWriter)

Возвращаясь к нашему HttpHandler, мы видим, что и handleRequest, и handleInvalidRequest используют статический метод writeResponse внутри класса ResponseWriter.

Это используется для записи объекта HttpResponse в выходной поток.

В этом примере мы просто пишем тело как text/plain, но на самом деле этот класс может использовать разные реализации для написания тела в зависимости от типа записываемых данных (JSON/XML и т. д.).

Шаги средства записи:

  1. Извлечение кода состояния.
  2. Извлечение описания кода состояния.
  3. Создание строк заголовка (например, «Content-Length: 50»).
  4. Запись первой строки (протокол, код состояния, описание кода состояния).
  5. Запись заголовков запроса в следующих строках.
  6. Запись пустой строки.
  7. Запись тела (если присутствует). )
/**
 * Write a HTTPResponse to an outputstream
 * @param outputStream - the outputstream
 * @param response - the HTTPResponse
 */
 public static void writeResponse(final BufferedWriter outputStream, final HttpResponse response) {
     try {
         final int statusCode = response.getStatusCode();
         final String statusCodeMeaning = HttpStatusCode.STATUS_CODES.get(statusCode);
         final List<String> responseHeaders = buildHeaderStrings(response.getResponseHeaders());
 
         outputStream.write("HTTP/1.1 " + statusCode + " " + statusCodeMeaning + "\r\n");
 
         for (String header : responseHeaders) {
             outputStream.write(header);
         }
 
         final Optional<String> entityString = response.getEntity().flatMap(ResponseWriter::getResponseString);
         if (entityString.isPresent()) {
             final String encodedString = new String(entityString.get().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
             outputStream.write("Content-Length: " + encodedString.getBytes().length + "\r\n");
             outputStream.write("\r\n");
             outputStream.write(encodedString);
         } else {
             outputStream.write("\r\n");
         }
     } catch (Exception ignored) {
     }
 }

Извлечение строк заголовка:

private static List<String> buildHeaderStrings(final Map<String, List<String>> responseHeaders) {
    final List<String> responseHeadersList = new ArrayList<>();

    responseHeaders.forEach((name, values) -> {
        final StringBuilder valuesCombined = new StringBuilder();
        values.forEach(valuesCombined::append);
        valuesCombined.append(";");

        responseHeadersList.add(name + ": " + valuesCombined + "\r\n");
    });

    return responseHeadersList;
}

Преобразуйте объект тела ответа в строку:

private static Optional<String> getResponseString(final Object entity) {
    // Currently only supporting Strings
    if (entity instanceof String) {
        try {
            return Optional.of(entity.toString());
        } catch (Exception ignored) {
        }
    }
    return Optional.empty();
}

В завершение давайте быстро рассмотрим Pojo HttpRequest/HttpResponse и протестируем наше приложение:

HttpRequest:

package Sockets.pojos;

import Sockets.contract.HttpMethod;

import java.net.URI;
import java.util.List;
import java.util.Map;

public class HttpRequest {
  private final HttpMethod httpMethod;
  private final URI uri;
  private final Map<String, List<String>> requestHeaders;

  private HttpRequest(HttpMethod opCode,
                     URI uri,
                     Map<String, List<String>> requestHeaders) {
      this.httpMethod = opCode;
      this.uri = uri;
      this.requestHeaders = requestHeaders;
  }

  public URI getUri() {
      return uri;
  }

  public HttpMethod getHttpMethod() {
      return httpMethod;
  }

  public Map<String, List<String>> getRequestHeaders() {
      return requestHeaders;
  }

  public static class Builder {
      private HttpMethod httpMethod;
      private URI uri;
      private Map<String, List<String>> requestHeaders;

     public Builder() {
     }

     public void setHttpMethod(HttpMethod httpMethod) {
         this.httpMethod = httpMethod;
     }

     public void setUri(URI uri) {
         this.uri = uri;
     }

     public void setRequestHeaders(Map<String, List<String>> requestHeaders) {
         this.requestHeaders = requestHeaders;
     }

     public HttpRequest build() {
         return new HttpRequest(httpMethod, uri, requestHeaders);
     }
  }
}

HttpResponse:

package Sockets.pojos;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class HttpResponse {
  private final Map<String, List<String>> responseHeaders;
  private final int statusCode;

  private final Optional<Object> entity;

  /**
   * Headers should contain the following:
   * Date: < date >
   * Server: < my server >
   * Content-Type: text/plain, application/json etc...
   * Content-Length: size of payload
   */
  private HttpResponse(final Map<String, List<String>> responseHeaders, final int statusCode, final Optional<Object> entity) {
      this.responseHeaders = responseHeaders;
      this.statusCode = statusCode;
      this.entity = entity;
  }
  public Map<String, List<String>> getResponseHeaders() {
      return responseHeaders;
  }
  public int getStatusCode() {
      return statusCode;
  }

  public Optional<Object> getEntity() {
      return entity;
  }

  public static class Builder {
      private final Map<String, List<String>> responseHeaders;
      private int statusCode;

      private Optional<Object> entity;

      public Builder() {
          // Create default headers - server etc
          responseHeaders = new HashMap<>();
          responseHeaders.put("Server", List.of("MyServer"));
          responseHeaders.put("Date", List.of(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))));

          entity = Optional.empty();
      }

      public Builder setStatusCode(final int statusCode) {
          this.statusCode = statusCode;
          return this;
      }

      public Builder addHeader(final String name, final String value) {
          responseHeaders.put(name, List.of(value));
          return this;
      }

      public Builder setEntity(final Object entity) {
          if (entity != null) {
              this.entity = Optional.of(entity);
          }
          return this;
      }

      public HttpResponse build() {
          return new HttpResponse(responseHeaders, statusCode, entity);
      }
  }

Тестирование!

Если мы загрузим наше приложение и сгенерируем HTTP-запрос GET к URI /testOne, мы должны увидеть следующий ответ:

➜  sockets git:(master) ✗ curl http://127.0.0.1:8080/testOne          
<HTML> <P> Hello There... </P> </HTML>%

К URI не определен:

➜  sockets git:(master) ✗ curl http://127.0.0.1:8080/testTwo
Route Not Found...%

и, наконец, неверный HTTP-запрос:

➜  sockets git:(master) ✗ echo hi | nc 127.0.0.1 8080 
HTTP/1.1 400 BAD_REQUEST
Server: MyServer;
Date: Mon, 9 Jan 2023 22:17:20 GMT;
Content-Length: 18

Invalid Request...%

Это лишь поверхностное представление о создании HTTP-сервера, но, надеюсь, в качестве учебного упражнения оно окажется полезным для лучшего понимания некоторых возможных шагов, происходящих за кулисами.

Весь упомянутый код можно найти здесь: https://github.com/AlexanderPanshin/SimpleJavaWebServer

 Нет комментариев    315   6 мес   java   server   web

Пример запуска JProgressBar в модальном окне.

Хотел найти рабочий пример запуска прогресс бара в модальном окне, попадались все какие-то не рабочие примеры. Назрел геморрой. По этому оставлю этот код тут:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ModalProgressBarExample extends JFrame {
    private JProgressBar progressBar;
    private JButton startButton;
    private JDialog dialog;
    private Timer timer;

    public ModalProgressBarExample() {
        setTitle("Modal Progress Bar Example");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 200);
        setLocationRelativeTo(null);

        progressBar = new JProgressBar();
        startButton = new JButton("Start");

        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Runnable show = () -> {
                    showProgressBar();
                };
                SwingUtilities.invokeLater(show);
                Runnable r = () -> {
                    startProgressBar();
                };
                r.run();
            }
        });

        JPanel panel = new JPanel();
        panel.add(startButton);

        add(panel, BorderLayout.CENTER);
    }

    private void showProgressBar() {
        dialog = new JDialog(this, "Progress", true);
        dialog.setLayout(new FlowLayout());
        dialog.setSize(200, 100);
        dialog.setLocationRelativeTo(null);

        progressBar = new JProgressBar(0, 100);
        progressBar.setValue(0);
        progressBar.setStringPainted(true);

        dialog.add(progressBar);
        dialog.setVisible(true);
    }

    private void startProgressBar() {
        timer = new Timer(100, new ActionListener() {
            int value = 0;

            @Override
            public void actionPerformed(ActionEvent e) {
                value++;
                progressBar.setValue(value);
                System.out.println(value);
                if (value == 100) {
                    timer.stop();
                    dialog.dispose();
                }
            }
        });

        timer.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ModalProgressBarExample().setVisible(true);
            }
        });
    }
}

И геморрой проходит.

 Нет комментариев    142   2023   java   JProgressBar   swing

Jar to exe

Сегодня речь пойдёт о десктоп программах написанных на java и их распространении. Со временем я столкнулся с проблемой — программы, которые я хочу показать знакомым не запускаются на их компьютере из-за отсутствия Java. Рассказывать о том как установить Java и запустить jar сборку — то ещё удовольствие. Особенно для пользователей Windows, которые привыкли запускать приложения двойным кликом.

В своё время я практиковал «exe» сборки собранные через Launch4j, но это не избавляло от необходимости установки Java. А рассказ знакомым про переменные среды и передачу аргументов заставлял их отказываться от идеи посмотреть мою программу.

Итак, ближе к делу, начиная с Java 9 появился славный инструмент jlink, который позволяет создать пользовательскую среду исполнения с минимальным количеством модулей. То есть свою Java с блек-джеком и распутными женщинами, которой мы будем передавать свой jar.

Для пользователя это будет выглядеть как «exe» файл и пара папок. И ни каких дополнительных установок делать не нужно. Ниже мы напишем самую простую программу на Java Swing (так как он встроен в Java), соберем Jar. Сделаем образ среды исполнения и «exe» файл. Даже со своим не скучным значком.

Программа и сборка jar:

Итак, создадим простое окно с надписью внутри Hello world . Откроем Ide, там создадим простой java проект, назову его SwingExempl.

Пример 1

Внутри напишем код для создания окна. Вот его листинг

import javax.swing.*;

public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {//Приложение Swing запускается в отдельном потоке событий. Это он:
@Override
public void run() {
JFrame frame = new JFrame("SwingExempl");//Создаём окно и задаём ему название:
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// Это для того, чтобы при нажатии на «x» окно закрывалось.
frame.setSize(300,100);//тут его размер
JPanel panel = new JPanel();// Это панель для окна
panel.add(new JLabel("Hello world"));// В панель вставляем надпись
frame.add(panel);//Вставляем в окно панель
frame.setVisible(true);//Делаем всё видимым
}
});
}

Тут я написал комментарий к коду, что-то писать дополнительно не имеет смысла.
Теперь нам надо создать Jar архив с помощью IntelliJ IDEA, это не сложно. Для этого перейдём File — Project Structure — Artifacts

Там нажмём кнопку «+», где выберем jar, а там вкладку From modules with dependencies...

Пример 2

В появившемся окне следует указать Main класс и нажать кнопку ок

Пример 3

Теперь можно собрать наш Jar, для этого выберем пункт Build — Build artifacts ...

Пример 4

В появившемся окне выбрать SwingExampl.jar — Build

Пример 5

Теперь всё в папке out/artifacts/SwingExempl_jar/ появился наш архив SwingExempl.jar.
Его можно запустить двойным кликом и убедиться, что программа работает.

Пример 5

Зависимости и среда исполнения:

Теперь нам нужно создать среду исполнения, это будет что-то типа JDK, но изрядно кастрированная. Для того, чтобы не пихать в неё все модули, нам нужно узнать текущие модули используемые в нашей программе. В этом нам поможет утилита jdeps.

В папке с нашим jar архивом вызовем терминал, где наберём команду

jdeps.exe -s SwingExempl.jar

В моем случае на скриншоте путь до утилиты jdeps указан абсолютный, так как в переменных средах у меня нет jdeps. У меня jdk 20 и утилита jdeps находится там.

Пример 7

В нашем случае утилита вывела всего две зависимости:

SwingExempl.jar -> java.base
SwingExempl.jar -> java.desktop

Естественно в больших проектах будет больше модулей, часть из них может быть подключена в виде jar библиотек. Тогда следует запаковать их в свой jar и прописать в файле манифеста.

Зная наши зависимости,
Посмотреть все изображения
собираем среду исполнения утилитой jlink

jlink.exe —no-header-files —no-man-pages —compress=2 —strip-debug —add-modules java.base,java.desktop,jdk.accessibility —output ваш_путь\out\artifacts\SwingExempl_jar\out3

Кстати, папка out3 не должна существовать, иначе утилита выдаст ошибку. То есть она создаст папку сама. В итоге у нас появляется директория out3, в которой есть папка bin, а там наша java.exe

Хотя утилита не писала модуль jdk.accessibility мы его добавим, иначе будет ошибка при попытке запуска программы.

Настало время её проверить, для этого запустим наш jar из той java, которую мы собрали. Команда будет такой:

ваш_путь\out\artifacts\SwingExempl_jar\out3\bin\java.exe -jar SwingExempl.jar

Пример 8

Мы запускаем программу не от java, которая у нас в переменных средах Path, а от java.exe, которую мы собрали.\

Если вы видите окно программы, значит все идёт по плану. Вам удалось собрать среду исполнения и она работает. Фактически эту папку out3 мы и будем давать всем, кому хотим показать нашу программу.

Осталось создать в ней папку jar в директории ваш_путь\out\artifacts\SwingExempl_jar\out3\ и в неё засунуть наш SwingExempl.jar и уже можно передавать программу так. Так как той java, которую мы создали, хватит для запуска программы. Но мы хотим «exe». Придётся немного попрограммировать на «С» и поставить mingw.

Делаем «exe», пишем на «С»:

Для начала нам нужно скачать компилятор mingw Я распаковал его в корень C:/, получилось как-то так:

Пример 9

Потом прописал его в «переменных средах» — «системных переменных» — «Переменная Path» там создадим ещё одну переменную с путём в папку bin C:\MinGW64\bin

Пример 10

Теперь проверим наш компилятор, для этого вводим в терминале:

gcc --version

Если вы видите что-то в духе названия и версии, то все хорошо. В противном случае поищите статьи про установку mingw

Пример 11

Теперь настало время создать «exe» файл. Для этого я создам в директории ваш_путь\out\artifacts\SwingExempl_jar\ папку ccfolder

Пример 12

В неё положу значок моего будущего «exe» файла. Это будет кружка с кофе. Значок должен быть в формате ICO, у меня он будет называться Java.ico. Тут же я создам файл с названием resources.rc
В него с помощью блокнота запишу:

001 ICON "Java.ico"

Как то так:

Пример 13

Сохраняем данные и потом компилируем файл с помощью утилиты windres.exe Команда будет выглядеть так:

ваш_путь\out\artifacts\SwingExempl_jar\ccfolder> windres —use-temp-file resources.rc resources.o

В результате будет создан файл resources.o в той же папке.
Теперь напишем маленькую программку на «С», в которой мы будем запускать нашу программу jar\SwingExempl.jar. Для этого создадим файл runjar.c, а внутри напишем следующее... Листинг ниже.

#include <stdio.h>
#include <process.h>

int main (int argc, char *argv[], char **penv) {
_execl("bin\\javaw.exe", "bin\\javaw.exe", "-jar", "jar\\SwingExempl.jar", NULL);
return 0;
}

Получится как то так:

Пример 14

Теперь заключительный момент, создадим «exe» файл сразу в папке out3 с нашим дистрибутивом. Он будет всего лишь запускать наш jar в пользовательской среде. Для этого введём команду:

ваш_путь\out\artifacts\SwingExempl_jar\ccfolder> gcc -Wall -O3 -mwindows -o ваш_путь\out\artifacts\SwingExempl_jar\out3\SwingExempl.exe runjar.c resources.o

После этого файл SwingExempl.exe должен появиться в папке out3, в той же папке в директории jar должен лежать наш SwingExempl.jar . На этом всё. Двойным кликом по SwingExempl.exe запускается наша простая программа. Структура каталога out3 выглядит так:

Пример 15

Теперь можно переименовать папку out3 как вам угодно, заархивировать и передавать её друзьям с windows , программа будет запускаться без установки jdk или jre. Правда вес нашей программы вырос многократно. Сама jar весит 2кб., а папка со средой и нашим архивом около 50мб. Но это маленькая плата за то, что нам не надо рассказывать как установить java .

PS: Методика взята из книги JavaFX в подлиннике (Николай Прохоренок). Исправлены опечатки, несколько переработан материал.

 Нет комментариев    166   2023   exe   Jar   java

Регулировка громкости звука midi в Java

Регулировка звука MIDI файлов JAVA я устал искать информацию вот краткая выжимка. Звук можно регулировать но не сильно не знаю почему значение 0 игнорируется.

И так
Sequencer это само устройства которое воспроизводит миди у него есть славные методы start и stop, беда в том что он не умеет регулировать звук

Sequence это последовательность миди данных в него мы пихаем наш миди файл а его засовываем в Sequencer, Sequence тоже не умеет менять звук.

Звук умеет менять синтезатор Synthesizer как достать синтизатор из сквенсера я не понял по этому я решил делать новый синтезатор и подключать его к сквенсору. И так как код изобилует try cath приведу его тут так

Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();

Получаем и открываем сквенсер

InputStream resource = Main.class.getResourceAsStream("Test.mid");

получаем инпутстрим на наш миди файл, у меня это написано так, потому что я собираю все Jar.

Sequence sequence = MidiSystem.getSequence(resource);

Засовываем инпутстрим куда вставлена миди в  sequence

Получим синтезатор и откроем его

Synthesizer synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();

Теперь возьмем передатчик сквенсера изменим ему приемник на приемник синтезатора

sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());

теперь засунем нашу мелодию в сквенсер

sequencer.setSequence(sequence)

Теперь получим каналы нашего синтезатора

MidiChannel[] channels = synthesizer.getChannels();

Поменяем на нем громкость в цикле на 60, в спеке говориться что диапазон значений от 0 до 120 и что за звук отвечает 7 канал

int volume = 60;
for (int i = 0; i < channels.length; i++) {
     channels[i].controlChange(7, volume);
}

Скажу сразу геморрой пройдет только от части, да значения звука будут меняться но не до конца очень тихо или громко сделать не получиться, слегка изменить уровень звука. Если есть знатоки миди то прошу оставить комментарий как можно нормально регулировать громкость миди.

full code

Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
InputStream resource = Main.class.getResourceAsStream("Test.mid");
Sequence sequence = MidiSystem.getSequence(resource);
Synthesizer synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
sequencer.setSequence(sequence)
MidiChannel[] channels = synthesizer.getChannels();
int volume = 60;
for (int i = 0; i < channels.length; i++) {
     channels[i].controlChange(7, volume);
}
sequencer.start();

Запуск Java приложений в Linux (Gnome) с поддержкой screen reader Orca

И так если вы пишите ГУИ на Джава и хотите сделать ваше приложение доступным в Линукс.
Но скринридер его не читает знай пришел конец твоему геморрою.
Проверь версию орки на момент написания статьи это была версия 45 стабильная 46 в альфе

dpkg -s orca | grep Version

Если версия новая нужно установить атк враперр командой

sudo apt-get install -y libatk-wrapper-java

Усе потом следует запускать джарник с аргументом

-Djavax.accessibility.assistive_technologies=org.GNOME.Accessibility.AtkWrapper

То есть запуск файл test.jar будет таким

java -jar -Djavax.accessibility.assistive_technologies=org.GNOME.Accessibility.AtkWrapper test.jar

И геморрой проходит

Создание jar фала с классом main в не корневого каталога

Столкунлься с проблемой когда хочу создать jar файл в [Java] где main метод [Java class] находиться в не корневой директории.
То есть класс майн распологаеться допустим так
\\
\\updater\Updater.class(main)
\\user\User.class
\\Minifest.txt
Файл манифест нам нужен для того что бы собрать jar в него будут записанны другие данные так что не переживайте что мы будем писать захардокренные пути. Так будет выглядить содержимое

Manifest-Version: 1.0
Main-Class: updater.Updater
Class-Path: file:///Disk:/project/updater

Еще не збудем в классе с методом main указать пакет
И в классе User

package updater;
package user;

потом собираем jar командой

jar cfm test.jar Manifest.txt  ./*/*.class

где

test.jar

это путь с именем файла где будет наш jar

Manifest.txt

Это файл манифеста на его основе будет создан манифест в jar

./*/*.class

Это файлы нашего проекта.

Вот и все проверяем это все командой

java -jar test.jar

И геморрой проходит!

 Нет комментариев    78   2023   java   код

Удаленная публикация постов Joomla

В joomla 4 появился rest API теперь новости можно публиковать удаленно .
Код класса который швыряет JSON в Joomla

import java.io.BufferedWriter;;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;


public class RePostreJ4 {
    private static String TOKEN_KEY = "X-Joomla-Token";
    private static String TYPE_KEY = "Content-Type";
    private static String TYPE_VALUE = "application/json;charset=UTF-8";
    private static String LANGUAGE ="\"language\":\"*\"";
    private String tokenValue;
    private String alias;
    private Integer catid;
    private  String metadesc;
    private String metakey;
    private FTPLoader ftp;
    private ParserText pt;



    private  String title = "";
    private String articletext= "";

    public RePostreJ4(String tokenValue, Integer catid, String metadesc, String metakey,ParserText pt) {
        this.tokenValue = tokenValue;
        this.catid = catid;
        this.metadesc = metadesc;
        this.metakey = metakey;
        this.pt = pt;
        this.title = pt.namePost;
        this.articletext = pt.getTextContent();

        this.alias = FTPLoader.Translator.translitor(pt.fileName);

    }
    public void goPost(String siteName){//Это отправляет пост запрос
        try {
            URL url = new URL(siteName+"/api/index.php/v1/content/articles");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");

            conn.setRequestProperty("Accept-Charset", "UTF-8");
            conn.setRequestProperty("Content-Language", "ru-RU");

            conn.setRequestProperty(TYPE_KEY,TYPE_VALUE);
            conn.setRequestProperty(TOKEN_KEY,tokenValue);
            conn.setDoOutput(true);
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), "UTF-8"));
            out.write(jsonCreater());
            out.flush();
            out.close();
            System.out.println(conn.getResponseMessage());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private String jsonCreater(){ //Этот метод создает строку JSON
        StringBuilder sb = new StringBuilder("{\"alias\":\"");
        sb.append(alias+"\",\"articletext\":\""+clear(articletext)+"\",\"catid\":"+catid+","+LANGUAGE+",\"metadesc\":\""+metadesc
                +"\",\"metakey\":\""+metakey+"\",\"title\": \""+title+"\"}");
        System.out.println(sb);
        return sb.toString();
    }
    private String clear(String stroka){//Это нужен так как если в строке будет " JSON сломаеться
        return stroka.replace("\"","'");
    }


}

Теперь можно постить посты удаленно, и геморрой проходит.

 Нет комментариев    40   2022   API   java   joomla

Тест скорости чуть по лучше

С пингом явно что то не так, с загрузкой файла результат корректнее.
У меня там ошибки в вычислениях скорости надо по править .

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

public class DownLoadTest {
    public static void main(String[] args) throws IOException {
        DownLoadTest downLoadTest =new DownLoadTest();
        long start1m = System.currentTimeMillis();
        downLoadTest.down1mb();
        long finish1m = System.currentTimeMillis();
        long elapsed1m = finish1m - start1m;
        downLoadTest.print1(elapsed1m);

        long start10m = System.currentTimeMillis();
        downLoadTest.down10mb();
        long finish10m = System.currentTimeMillis();
        long elapsed10m = finish10m - start10m;
        downLoadTest.print10(elapsed10m);


    }
    public void down10mb() throws IOException {
        URL website = new URL("https://gemorr.online/test10");
        ReadableByteChannel rbc = Channels.newChannel(website.openStream());
        FileOutputStream fos = new FileOutputStream("information.html");
        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
    }
    public void down1mb() throws IOException {
        URL website = new URL("https://gemorr.online/TGv97zVdw8kxG9iT8fuo");
        ReadableByteChannel rbc = Channels.newChannel(website.openStream());
        FileOutputStream fos = new FileOutputStream("information.html");
        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
    }
    public void print1(long time ){
        System.out.println("Ушло время на загрузку пакета 1 мегабайт "+time);
        System.out.println(Mega.MEGA1.mega/time/100+ "Mbs test 1 mb");
        System.out.println(Mega.MEGA1.mega/time +" Kbs test 1mb" );
        System.out.println(Mega.MEGA1.mega/time*1000+" bs test 1mb");
    }
    public void print10(long time ){
        System.out.println("Ушло время на загрузку пакета 10 мегабайт "+time);
        System.out.println(Mega.MEGA10.mega/time/100+ "Mbs test 10 mb");
        System.out.println(Mega.MEGA10.mega/time +" Kbs test 10mb" );
        System.out.println(Mega.MEGA10.mega/time*1000+" bs test 10mb");
    }

}
enum Mega{
    MEGA1(1048576),
    MEGA10(10485760);
    public final long mega;
    Mega(int mega){
        this.mega = mega;
    }


}

Измерение скорости по Ping

Прочитал такую легенду сто скорость интернета можно интернета можно косвенно померить по пингу.

Скорость сети равна ≈ (отправлено байтов / время возврата [мс]) К байтов

Написал тестовую программку на JAVA это увы не так

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class PingTest {
    public static void main(String args[])
    {
        String addrs= "www.baidu.com";
                String line = null;
                try
                {
                    Process pro = Runtime.getRuntime().exec("ping " + addrs+" -l 1000 -n 4");
                    BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream(),"cp866"));
                    while((line = buf.readLine()) != null){

                        int position=0;
                        if((position=line.indexOf("Среднее"))>=0)
                        {
                            System.out.println(new String(line.getBytes("UTF-8"), "windows-1251"));
                            String value=line.substring(position+10,line.lastIndexOf("мсек")-1);
                            System.out.println(new String("    Ваша скорость :".getBytes("UTF-8"), "windows-1251")+(1000/Integer.parseInt(value))+"KB");

                        }
                    }
                }
                catch(Exception ex)
                {
                    System.out.println(ex.getMessage());
                }
            }
        }

Скорость меряет не верно

jshell windows 10

Хотелось для отладки использовать jshell в windows 10 и что бы не писать полный путь, до утилиты.
Я в поиске windows нахожу где у мня установлен jshell в моем случаи это

C:\Program File\Java\jdk-14\bin

Копирую эту строку.
Вызываю пуск->выполнить пишу там cmd тыкаю интер
В командной строке набираю

SET PATH=C:\Program File\Java\jdk-14\bin;%PATH%

Теперь в командной строке могу запускать утилиту просто набрав jshell
И нет геморроя.

 Нет комментариев    182   2020   java   jshell   windows 10