Жопа кота

Блог геморроя

Я устал создавать куча заметок и забывать пароли от них. Для этого я создал этот микроблог. Буду писать то что не плохо было бы помнить.

Труд Всем

С министерством труда что-то совсем ничего не понятно. Решил сделать свою выборку через API.
Почему-то у них 2 сайта
https://trudvsem.ru
https://mintrud.ru/
И на 2 сайтов количество вакансий разное
А по api я получаю в 2 раза меньше вакансий.
Не ясно какое количество вакансий реально все источники выдают разный результат.
Геморрой какой-то

ecompile your kernel and reboot (VERR_SVM_IN_USE). VirtualBox Fedora

Это из за нового ядра. Редактируем

sudo nano /etc/default/grub

Вписываем в строку GRUB_CMDLINE_LINUX параметр kvm.enable_virt_at_load=0

GRUB_CMDLINE_LINUX="kvm.enable_virt_at_load=0"

Если в строке что то было впишите через пробел параметр

GRUB_CMDLINE_LINUX="blabla bla kvm.enable_virt_at_load=0"

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

Доступ root по ftp

добавляем в конфигурацию /etc/vsftpd.conf

userlist_deny=YES

И за комментируем #root в файле /etc/ftpusers
пере запускаем сервис

systemctl restart vsftpd

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

 Нет комментариев    52   1 мес   ftp   root

Авто монтирования диска webdaw

Каждый раз надоедает подключать сетевой диск. Геморрой скоро пройдёт. Вот эту команду надо ввести в командную строку. Естественно поменяв username и password команда для яндекс диска.

net use O: https://webdav.yandex.ru/ password /user:username /persistent:yes

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

  1. Нажмите `Win + S`, введите «Планировщик задач» и откройте его.
  2. В правой части окна выберите «Создать задачу...» (Create Task).
  3. Вкладка Общие:
    • Укажите имя задачи (например, «Монтировать WebDAV»).
    • Установите флажок «Запускать с наивысшими правами» (Run with highest privileges).
  4. Вкладка Триггеры:
    • Нажмите «Создать...» (New).
    • В поле «Начать задачу» (Begin the task) выберите «При входе в систему» (At log on).
    • Нажмите «ОК».
  5. Вкладка Действия:
    • Нажмите «Создать...» (New).
    • В поле «Действие» (Action) выберите «Запуск программы» (Start a program).
    • В поле «Программа или сценарий» (Program/script) укажите «net».
    • В поле «Добавить аргументы» в вести «use O: https://webdav.yandex.ru/ password /user:login»
    • Нажмите «ОК».
  6. Вкладка Условия:
    • Вы можете отключить опцию «Запускать задачу только при питании от сети» (Start the task only if the computer is on AC power), если хотите, чтобы задача выполнялась и при работе от батареи.
  7. Нажмите «ОК», чтобы сохранить задачу.
    А так проходит.

Остановка плеера при смене источника Icecast

Если вы испытываете проблему в том что при смене источника останавливается воспроизведение в плеере.
Знайте геморрой скоро пройдет.

  1. Источники должны иметь один и тот же формат битрейт и прочие характеристики.
  2. Если вы вещаете в ogg точки монтирование должны чётко указывать расширение /stream.ogg

Иначе плеер будет их воспринимать как mp3
Это относиться и к резервным каналам.
И геморрой проходит.

2 роутера с разными провайдерами в одну сеть.

Или другими словами 2 wan в 1 lan. Если хочется иметь двух провайдеров и одни общие ресурсы в сети. Роутер 1 (192.168.1.1)(провайдер 1) Обычные настройки включен DHCP адреса от 192.168.1.100 — 254. Роутер 2 192.168.1.2 (провайдер 2) выключен dhcp. Каждый провайдер подключен к wan порту своего роутера. Между роутерами стоит перемычка между lan 1 роутера 1 и lan 1 роутера 2. Остальные провода можете подключать в любые порта. Все подключенные устройства будут работать через провайдера 1. Что бы устройство работало через провайдера 2 нужно в настройках его интернет соединения отключить dhcp. В качестве основного шлюза указать роутер с провайдером 2(192.168.1.2) в качестве адреса самого устройства адрес в не предела dhcp то есть от 3 — 99 маска 255.255.255.0. Все теперь клиент с такими настройками будет подключаться через провайдера 2. Теперь делаем такие настройки у каждого устройства нужного подключить через провайдера 2. Помните что двух устройств с одинаковым ip в сети быть не может по этому адресу самого устройства нужно прибавить 1 по сравнению с предыдущим. Все геморрой проходит теперь у вас 2 провайдера и одна локальная сеть.

P.s. скорость интернета за счёт этого не вырастит. Но вы сможете разгрузить канал если у вас много устройств с большим потреблением трафика типо ТВ. Для агрегация сети что бы скорость скорость интернета складывалась смотрите в сторону микротика и балансировки нагрузки. И мне удавалось увидеть на спидтесте цифро 200 (два провайдера по 100) алгоритм round robin. Но вас ждёт попа боль так ты понятия не имеешь с какого айпи ты зайдешь на следующей сайт . И не каждый сайт понимает запросы с разных ИП. Короче не советую.

Создание 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

Установка open office на kali linux

Внимание если установлен пакет libreoffice то openoffice не установиться если вы хотите установить open office взамен libre сначало удалите его командой

sudo apt-get remove libreoffice*

Для начала нам следует скачать deb сборку с официального сайта

https://www.openoffice.org/download/

Там в выпадающем меню следует выбрать DEB пакет язык русский и желательно последнею версию. На момент написания статьи это 4,15

после распакуем архив можно сразу в папке Download потом переходим в папку

cd /Downloads/ru/DEBS

установим все пакеты deb следующей командой

sudo dpkg -i *.deb

Далее следует перейти в папку

cd /Downloads/ru/DEBS/desktop-integration

и повторить команду

sudo dpkg -i *.deb

На этом геморрой проходит и установка завершена.

Скачать Шрифт Брайеля TTF

Шрифт Брайеля можно использовать для автоматического перевода из обычного текста в шрифт Брайеля прямо в текстовом редакторе Word или ему подобным. Для этого выделите текст и выберете среди Шрифтов Kanischev_Braille. Не поддерживает математические символы.
Скачать
Post Scriptum
Более подробная информация по математическим символам в шрифте Брайеля тут

Ранее Ctrl + ↓