О насБлогКонтакты
Технологии8 июня 1999 г. 7 мин 106Обновлено: 18 мая 2026 г.

Java-апплеты: интерактивный веб в Бишкеке 1999 года

AunimedaAunimeda
📋 Содержание

Java-апплеты: интерактивный веб в Бишкеке 1999 года

1999 год. Бишкек. Интернет через провайдера ElCat или AsiaInfo - dial-up, 28-33 кбит/с. Задержка до ближайшего крупного сервера: 150-250 мс до Москвы, 250-400 мс до Европы. Загрузить обычную веб-страницу с парой картинок - 20-30 секунд. Загрузить Java-апплет - ещё минута сверху.

И всё равно мы их писали.

Потому что в 1999 году никакой другой технологии, позволявшей создать интерактивную графику прямо в браузере, просто не существовало. JavaScript умел только менять тексты и переключать картинки. Flash требовал отдельного платного инструментария. Java был открытым, бесплатным, и - теоретически - «работал везде».


Архитектура: JDK 1.1, AWT и двойная буферизация

// VisitorCounterApplet.java - счётчик посещений с анимацией
// JDK 1.1, AWT, Бишкек 1999 год
// Применение: "живой" счётчик на главной странице компании

import java.applet.Applet;
import java.awt.*;
import java.net.*;
import java.io.*;

public class VisitorCounterApplet extends Applet implements Runnable {

    private int    count       = 0;
    private String label       = "Посетителей сегодня:";
    private Font   bigFont;
    private Font   smallFont;
    private Thread loadThread;
    private boolean loading    = true;

    @Override
    public void init() {
        String paramLabel = getParameter("label");
        if (paramLabel != null) label = paramLabel;

        bigFont   = new Font("SansSerif", Font.BOLD, 28);
        smallFont = new Font("SansSerif", Font.PLAIN, 11);
        setBackground(new Color(0, 33, 99));
    }

    @Override
    public void start() {
        // Загрузить счётчик с сервера в отдельном потоке
        // (прямой HTTP-запрос из апплета к тому же хосту)
        loadThread = new Thread(this);
        loadThread.start();
    }

    @Override
    public void run() {
        try {
            // Апплет мог обращаться только к своему хосту - sandbox ограничение
            URL url = new URL(getCodeBase(), "/cgi-bin/counter.pl?output=plain");
            URLConnection conn = url.openConnection();
            conn.setConnectTimeout(5000);

            BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream())
            );
            String line = reader.readLine();
            reader.close();

            if (line != null && line.matches("\\d+")) {
                count = Integer.parseInt(line.trim());
            }
        } catch (Exception e) {
            count = -1;   // ошибка загрузки
        } finally {
            loading = false;
            repaint();
        }
    }

    @Override
    public void paint(Graphics g) {
        Dimension d = getSize();

        g.setColor(new Color(0, 33, 99));
        g.fillRect(0, 0, d.width, d.height);

        if (loading) {
            g.setColor(Color.gray);
            g.setFont(smallFont);
            g.drawString("Загрузка...", 10, d.height / 2);
            return;
        }

        g.setColor(new Color(255, 215, 0));   // золотой
        g.setFont(smallFont);
        g.drawString(label, 8, 16);

        if (count >= 0) {
            g.setFont(bigFont);
            g.setColor(Color.white);
            String countStr = formatNumber(count);
            FontMetrics fm = g.getFontMetrics();
            int x = (d.width - fm.stringWidth(countStr)) / 2;
            g.drawString(countStr, x, d.height - 8);
        } else {
            g.setColor(Color.red);
            g.setFont(smallFont);
            g.drawString("Ошибка загрузки", 8, d.height - 8);
        }
    }

    private String formatNumber(int n) {
        // Форматирование числа с пробелами (1 234 567)
        String s = String.valueOf(n);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            if (i > 0 && (s.length() - i) % 3 == 0) sb.append(' ');
            sb.append(s.charAt(i));
        }
        return sb.toString();
    }

    // Двойная буферизация - без этого в AWT мерцало
    private Image buf; private Graphics bufG;

    @Override
    public void update(Graphics g) {
        Dimension d = getSize();
        if (buf == null) { buf = createImage(d.width, d.height); bufG = buf.getGraphics(); }
        paint(bufG);
        g.drawImage(buf, 0, 0, this);
    }
}
<!-- Вставка на главную страницу компании в Бишкеке, 1999 год -->
<applet code="VisitorCounterApplet.class"
        archive="counter.jar"
        width="200"
        height="60"
        alt="Счётчик посещений">
  <param name="label" value="Посетителей сегодня:">
  <!-- Фолбэк: старый добрый GIF-счётчик -->
  <img src="/cgi-bin/counter.pl?output=gif" alt="Счётчик">
</applet>

Более серьёзный апплет: интерактивный прайс-лист

// PriceListApplet.java - кликабельный прайс с сортировкой
// Актуально для торговых компаний Бишкека 1999 года

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class PriceListApplet extends Applet implements MouseListener {

    // Данные: название товара, цена в сомах
    private String[][] items = {
        {"Pentium II 350 МГц",     "18500"},
        {"Pentium II 450 МГц",     "24000"},
        {"HDD 4.3 ГБ Seagate",     "5800"},
        {"HDD 8.4 ГБ WD",          "8200"},
        {"RAM 64 МБ SDRAM",         "3400"},
        {"Монитор 15\" Samsung",    "14500"},
        {"Модем 56k US Robotics",   "6800"},
    };

    private int    selectedRow = -1;
    private int    sortCol     = 0;
    private boolean sortAsc    = true;

    private static final int ROW_H  = 22;
    private static final int COL1_W = 220;
    private static final int COL2_W = 100;
    private static final int HDR_H  = 24;

    @Override
    public void init() {
        addMouseListener(this);
        setBackground(Color.white);
    }

    @Override
    public void paint(Graphics g) {
        // Заголовок таблицы
        g.setColor(new Color(0, 51, 153));
        g.fillRect(0, 0, COL1_W + COL2_W, HDR_H);

        g.setColor(Color.white);
        g.setFont(new Font("SansSerif", Font.BOLD, 11));
        g.drawString("Товар" + (sortCol == 0 ? (sortAsc ? " ▲" : " ▼") : ""), 8, HDR_H - 6);
        g.drawString("Цена (сом)" + (sortCol == 1 ? (sortAsc ? " ▲" : " ▼") : ""),
                     COL1_W + 4, HDR_H - 6);

        // Строки
        g.setFont(new Font("SansSerif", Font.PLAIN, 11));
        for (int i = 0; i < items.length; i++) {
            int y = HDR_H + i * ROW_H;

            if (i == selectedRow) {
                g.setColor(new Color(204, 221, 255));
            } else {
                g.setColor(i % 2 == 0 ? Color.white : new Color(245, 245, 245));
            }
            g.fillRect(0, y, COL1_W + COL2_W, ROW_H);

            g.setColor(Color.black);
            g.drawString(items[i][0], 8, y + ROW_H - 6);
            g.drawString(items[i][1], COL1_W + 4, y + ROW_H - 6);

            // Горизонтальная линия
            g.setColor(new Color(220, 220, 220));
            g.drawLine(0, y + ROW_H, COL1_W + COL2_W, y + ROW_H);
        }

        // Вертикальный разделитель
        g.setColor(new Color(200, 200, 200));
        g.drawLine(COL1_W, 0, COL1_W, HDR_H + items.length * ROW_H);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        int y = e.getY();

        if (y < HDR_H) {
            // Клик по заголовку - сортировка
            int newSortCol = e.getX() < COL1_W ? 0 : 1;
            if (newSortCol == sortCol) {
                sortAsc = !sortAsc;
            } else {
                sortCol = newSortCol;
                sortAsc = true;
            }
            sortItems();
            repaint();
            return;
        }

        int row = (y - HDR_H) / ROW_H;
        if (row >= 0 && row < items.length) {
            selectedRow = row;
            showStatus("Выбрано: " + items[row][0] + " - " + items[row][1] + " сом");
            repaint();
        }
    }

    private void sortItems() {
        final int col   = sortCol;
        final boolean asc = sortAsc;
        Arrays.sort(items, new Comparator<String[]>() {
            public int compare(String[] a, String[] b) {
                int result;
                if (col == 1) {
                    result = Integer.compare(
                        Integer.parseInt(a[1]),
                        Integer.parseInt(b[1])
                    );
                } else {
                    result = a[0].compareToIgnoreCase(b[0]);
                }
                return asc ? result : -result;
            }
        });
    }

    @Override public void mousePressed(MouseEvent e)  {}
    @Override public void mouseReleased(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e)  {}
    @Override public void mouseExited(MouseEvent e)   {}
}

Проблемы с JVM в Кыргызстане 1999 года

Бишкекские разработчики столкнулись с теми же проблемами JVM, что и весь мир, но с местной спецификой:

Установка JVM у пользователей. Большинство пользователей в Бишкеке работали с IE4/IE5, у которых был встроенный Microsoft JVM. Но если пользователь скачал Netscape - нужно было отдельно устанавливать плагин Sun JVM (~10 МБ). По dial-up это было 30-40 минут загрузки. Многие просто не делали этого.

// Проверить версию JVM и показать сообщение если старая:
String version = System.getProperty("java.version");
// java.version = "1.1.8" или "1.2.2" и т.д.

if (version.startsWith("1.0") || version.startsWith("1.1")) {
    // Старая JVM - некоторые API могут отсутствовать
    // В Кыргызстане в 1999 встречалась JVM 1.0 на старых машинах
    showStatus("Рекомендуется обновить Java до версии 1.2 или выше");
}

Кириллица в апплетах. AWT-шрифты в JVM не всегда корректно отображали кириллицу:

// Шрифт "Dialog" в JDK 1.1 на русском Windows 98 - кириллица работала
// На немецком сервере с Sun JVM - кириллица могла не отображаться
// Решение: использовать только системные шрифты без явного задания

// Проблемный вариант:
g.setFont(new Font("Arial", Font.PLAIN, 12));   // Arial мог не иметь кириллицы

// Надёжный вариант для 1999 года:
g.setFont(new Font("Dialog", Font.PLAIN, 12));  // Dialog = системный шрифт Java

2001: Flash победил, Java ушла в бэкенд

К 2001 году в Бишкеке, как и везде, стало ясно: Flash выигрывает нишу интерактивного веба. Причины для Кыргызстана были особенно весомыми: Flash-плагин уже был в комплекте с Netscape и IE, Flash-ролик весил в 5-10 раз меньше JAR-файла, а инструментарий Macromedia не требовал знания Java.

Но опыт написания апплетов в 1999-2000 годах не прошёл бесследно. Разработчики, освоившие многопоточность, событийную модель и рисование в AWT, в 2008 году легко перешли на Android-разработку. Жизненный цикл Activity (onCreate/onStart/onStop/onDestroy) - прямой потомок жизненного цикла апплета (init/start/stop/destroy). Архитектура не устарела - сменилась платформа.

Читайте также

Первые сайты на PHP3 в 2002: как мы верстали до CSS, без фреймворков и без Stack Overflowaunimeda
Технологии

Первые сайты на PHP3 в 2002: как мы верстали до CSS, без фреймворков и без Stack Overflow

В 2002 году не было CSS Zen Garden, не было jQuery, не было документации на русском. Был PHP 3/4, были таблицы для вёрстки, был Dreamweaver с WYSIWYG, и было сообщество на форуме phpclub.ru. Вот как выглядела реальная веб-разработка 20 лет назад.

DHTML-навигация в Бишкеке 2001 года: выпадающие меню на чистом JavaScriptaunimeda
Технологии

DHTML-навигация в Бишкеке 2001 года: выпадающие меню на чистом JavaScript

В 2001 году выпадающее меню без Flash и без перезагрузки страницы - это было то, что отличало профессиональный бишкекский сайт от любительского. DHTML на JavaScript + CSS. Работало в IE5 и Netscape 6. Вот как мы это делали, что ломалось и почему 300 мс до сих пор актуально.

Classic ASP и первые корпоративные веб-системы Бишкека (2000)aunimeda
Технологии

Classic ASP и первые корпоративные веб-системы Бишкека (2000)

В 2000 году первые бишкекские компании задумались о системах онлайн-авторизации: закрытые разделы сайта, личные кабинеты для клиентов, защищённые прайс-листы. Те, у кого были Windows-серверы, выбирали Classic ASP. Полный код системы входа, реалии разработки в Кыргызстане 2000 года.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в Кыргызстане. Бесплатная консультация.

Получить консультацию Все статьи