main
commit
8d15e6987f
@ -0,0 +1,7 @@ |
||||
version: 2 |
||||
updates: |
||||
- package-ecosystem: npm |
||||
directory: "/" |
||||
schedule: |
||||
interval: daily |
||||
open-pull-requests-limit: 20 |
@ -0,0 +1,8 @@ |
||||
.DS_Store |
||||
Thumbs.db |
||||
db.json |
||||
*.log |
||||
node_modules/ |
||||
public/ |
||||
.deploy*/ |
||||
themes |
@ -0,0 +1,7 @@ |
||||
Copyright (c) 2013 Tommy Chen |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,90 @@ |
||||
# Hexo Theme Unit Test |
||||
|
||||
This is a dummy Hexo site for theme unit test. You should test your theme before release. |
||||
|
||||
This test doesn't contain the default theme. You have to install the theme you want to test before starting. |
||||
|
||||
## Usage |
||||
|
||||
1. Clone this repository |
||||
|
||||
``` bash |
||||
$ git clone https://github.com/hexojs/hexo-theme-unit-test.git |
||||
``` |
||||
|
||||
2. Install your own theme and modify `theme` setting in `_config.yml`. |
||||
3. Run server and start testing. Make sure all styles are displayed properly. |
||||
4. Once test is done, you can [submit your theme](https://hexo.io/docs/themes.html#Publishing)! |
||||
|
||||
## Checklist |
||||
|
||||
### `<head>` |
||||
|
||||
- Use the proper [DOCTYPE](https://en.wikipedia.org/wiki/Document_Type_Declaration). |
||||
If you don't know which doctype you should use, `<!DOCTYPE html>` is recommended. |
||||
- UTF8 charset |
||||
|
||||
``` html |
||||
<meta charset="utf-8"> |
||||
``` |
||||
|
||||
- Proper titles for different pages |
||||
- Favicon support |
||||
|
||||
``` html |
||||
<link rel="icon" href="path/of/favicon"> |
||||
``` |
||||
|
||||
### Index |
||||
|
||||
- Only display excerpts. (Better with a "Read More" link) |
||||
- [Pagination](https://hexo.io/docs/configuration.html#Pagination) |
||||
|
||||
### Post |
||||
|
||||
- Display post categories and tags. |
||||
- Disqus comment support. |
||||
- Display the post date. |
||||
- Support `photo` and `link` layout. |
||||
- Posts without title should be accessible. |
||||
|
||||
### Performance |
||||
|
||||
- Use [fragment_cache](https://hexo.io/docs/helpers.html#fragment_cache) |
||||
It caches render result across post/pages, see [#1769](https://github.com/hexojs/hexo/issues/1769) for the impact |
||||
|
||||
### Optional |
||||
|
||||
- Responsive design |
||||
- i18n |
||||
- Post share |
||||
- SEO |
||||
- RSS [Autodiscovery](https://www.rssboard.org/rss-autodiscovery) support |
||||
* Example: |
||||
``` html |
||||
<link rel="alternate" href="path/of/rss" type="application/atom+xml"> |
||||
``` |
||||
* Some RSS plugins (e.g. [hexo-generator-feed](https://github.com/hexojs/hexo-generator-feed) 2.1+) insert autodiscovery by default. There is a slight performance benefit if a theme inserts it, instead of the plugin. To take advantage of that, autodiscovery needs to be disabled in the plugin. |
||||
``` yml |
||||
feed: |
||||
autodiscovery: false |
||||
``` |
||||
* hexo-generator-feed plugin could generate more than one type of RSS (e.g. Atom & RSS2). Here is an example EJS snippet for multi-format support by utilizing [`feed_tag`](https://hexo.io/docs/helpers#feed-tag) helper: |
||||
``` js |
||||
<%- feed_tag() %> |
||||
``` |
||||
* If you want to support other plugins, in addition to hexo-generator-feed: |
||||
``` js |
||||
<% if (config.feed) { %> |
||||
<%- feed_tag() %> |
||||
<% } else if (theme.rss) { %> |
||||
<%- feed_tag(theme.rss) %> |
||||
<% } %> |
||||
``` |
||||
* If you decide to support autodiscovery, we recommend checking the updates of [hexo-generator-feed](https://github.com/hexojs/hexo-generator-feed/releases) (or any other RSS plugin that your theme prefers) from time to time. The configuration and functionality of an RSS plugin may change over time. |
||||
|
||||
## Resources |
||||
|
||||
- [Theme](https://hexo.io/docs/themes.html) |
||||
- [Variables](https://hexo.io/docs/variables.html) |
||||
- [Helpers](https://hexo.io/docs/helpers.html) |
@ -0,0 +1,105 @@ |
||||
# Hexo Configuration |
||||
## Docs: https://hexo.io/docs/configuration.html |
||||
## Source: https://github.com/hexojs/hexo/ |
||||
|
||||
# Site |
||||
title: Hexo |
||||
subtitle: '' |
||||
description: '' |
||||
keywords: |
||||
author: John Doe |
||||
language: en |
||||
timezone: '' |
||||
|
||||
# URL |
||||
## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project' |
||||
url: http://example.com |
||||
permalink: :year/:month/:day/:title/ |
||||
permalink_defaults: |
||||
pretty_urls: |
||||
trailing_index: true # Set to false to remove trailing 'index.html' from permalinks |
||||
trailing_html: true # Set to false to remove trailing '.html' from permalinks |
||||
|
||||
# Directory |
||||
source_dir: source |
||||
public_dir: public |
||||
tag_dir: tags |
||||
archive_dir: archives |
||||
category_dir: categories |
||||
code_dir: downloads/code |
||||
i18n_dir: :lang |
||||
skip_render: |
||||
|
||||
# Writing |
||||
new_post_name: :title.md # File name of new posts |
||||
default_layout: post |
||||
titlecase: false # Transform title into titlecase |
||||
external_link: |
||||
enable: true # Open external links in new tab |
||||
field: site # Apply to the whole site |
||||
exclude: '' |
||||
filename_case: 0 |
||||
render_drafts: false |
||||
post_asset_folder: false |
||||
relative_link: false |
||||
future: true |
||||
highlight: |
||||
enable: true |
||||
line_number: true |
||||
auto_detect: false |
||||
tab_replace: '' |
||||
wrap: true |
||||
hljs: false |
||||
prismjs: |
||||
enable: false |
||||
preprocess: true |
||||
line_number: true |
||||
tab_replace: '' |
||||
|
||||
# Home page setting |
||||
# path: Root path for your blogs index page. (default = '') |
||||
# per_page: Posts displayed per page. (0 = disable pagination) |
||||
# order_by: Posts order. (Order by date descending by default) |
||||
index_generator: |
||||
path: '' |
||||
per_page: 10 |
||||
order_by: -date |
||||
|
||||
# Category & Tag |
||||
default_category: uncategorized |
||||
category_map: |
||||
tag_map: |
||||
|
||||
# Metadata elements |
||||
## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta |
||||
meta_generator: true |
||||
|
||||
# Date / Time format |
||||
## Hexo uses Moment.js to parse and display date |
||||
## You can customize the date format as defined in |
||||
## http://momentjs.com/docs/#/displaying/format/ |
||||
date_format: YYYY-MM-DD |
||||
time_format: HH:mm:ss |
||||
## updated_option supports 'mtime', 'date', 'empty' |
||||
updated_option: 'mtime' |
||||
|
||||
# Pagination |
||||
## Set per_page to 0 to disable pagination |
||||
per_page: 10 |
||||
pagination_dir: page |
||||
|
||||
# Include / Exclude file(s) |
||||
## include:/exclude: options only apply to the 'source/' folder |
||||
include: |
||||
exclude: |
||||
ignore: |
||||
|
||||
# Extensions |
||||
## Plugins: https://hexo.io/plugins/ |
||||
## Themes: https://hexo.io/themes/ |
||||
theme: hexo-theme-yorha |
||||
|
||||
# Deployment |
||||
## Docs: https://hexo.io/docs/one-command-deployment |
||||
deploy: |
||||
type: '' |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@ |
||||
{ |
||||
"name": "hexo-site", |
||||
"version": "0.0.0", |
||||
"private": true, |
||||
"hexo": { |
||||
"version": "6.3.0" |
||||
}, |
||||
"dependencies": { |
||||
"hexo": "^6.3.0", |
||||
"hexo-browsersync": "^0.3.0", |
||||
"hexo-generator-archive": "^2.0.0", |
||||
"hexo-generator-category": "^2.0.0", |
||||
"hexo-generator-index": "^3.0.0", |
||||
"hexo-generator-tag": "^2.0.0", |
||||
"hexo-renderer-ejs": "^2.0.0", |
||||
"hexo-renderer-marked": "^6.1.0", |
||||
"hexo-renderer-pug": "^3.0.0", |
||||
"hexo-renderer-stylus": "^3.0.0", |
||||
"hexo-server": "^3.0.0", |
||||
"hexo-tag-embed": "latest" |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
title: {{ title }} |
||||
tags: |
||||
--- |
@ -0,0 +1,3 @@ |
||||
title: {{ title }} |
||||
date: {{ date }} |
||||
--- |
@ -0,0 +1,5 @@ |
||||
layout: {{ layout }} |
||||
title: {{ title }} |
||||
date: {{ date }} |
||||
tags: |
||||
--- |
@ -0,0 +1,4 @@ |
||||
title: {{ title }} |
||||
date: {{ date }} |
||||
tags: |
||||
--- |
@ -0,0 +1,20 @@ |
||||
title: 小更一条 ( ̄_ ̄|||) |
||||
author: lenfrex |
||||
cover: https://res.ciduid.top/blog/covers/cover(4).jpg |
||||
tags: |
||||
- 闲聊 |
||||
- 日常 |
||||
categories: |
||||
- 闲聊 |
||||
- 日常 |
||||
date: 2022-05-15 08:49:00 |
||||
--- |
||||
最近没什么时间打理这里了,所以打算断更一段很长的时间。 |
||||
|
||||
nginx的坑也许不会再填了.... |
||||
|
||||
*~~(其实还有一个关于linux权限的坑但是由于一直保存在草稿中所以就不算是坑了吧)~~* |
||||
|
||||
主题可能会换换其他的试试 |
||||
|
||||
就这样吧 |
@ -0,0 +1,9 @@ |
||||
title: Categories |
||||
date: 2013-12-24 23:30:09 |
||||
categories: |
||||
- Foo |
||||
- Bar |
||||
- Baz |
||||
--- |
||||
|
||||
This post contains 3 categories. Make sure your theme can display all of the categories. |
@ -0,0 +1,285 @@ |
||||
--- |
||||
title: Code Highlight Style test |
||||
date: 2019-07-25 16:43:00 |
||||
tags: |
||||
--- |
||||
|
||||
Make sure all the code blocks highlighted correctly. All the code samples are come from the demo of https://highlightjs.org |
||||
|
||||
<!-- more --> |
||||
|
||||
``` plain |
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. |
||||
``` |
||||
|
||||
```rust |
||||
#[derive(Debug)] |
||||
pub enum State { |
||||
Start, |
||||
Transient, |
||||
Closed, |
||||
} |
||||
|
||||
impl From<&'a str> for State { |
||||
fn from(s: &'a str) -> Self { |
||||
match s { |
||||
"start" => State::Start, |
||||
"closed" => State::Closed, |
||||
_ => unreachable!(), |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
```json |
||||
[ |
||||
{ |
||||
"title": "apples", |
||||
"count": [12000, 20000], |
||||
"description": {"text": "...", "sensitive": false} |
||||
}, |
||||
{ |
||||
"title": "oranges", |
||||
"count": [17500, null], |
||||
"description": {"text": "...", "sensitive": false} |
||||
} |
||||
] |
||||
``` |
||||
|
||||
```python |
||||
@requires_authorization |
||||
def somefunc(param1='', param2=0): |
||||
r'''A docstring''' |
||||
if param1 > param2: # interesting |
||||
print 'Gre\'ater' |
||||
return (param2 - param1 + 1 + 0b10l) or None |
||||
|
||||
class SomeClass: |
||||
pass |
||||
|
||||
>>> message = '''interpreter |
||||
... prompt''' |
||||
``` |
||||
|
||||
```html |
||||
<!DOCTYPE html> |
||||
<title>Title</title> |
||||
|
||||
<style>body {width: 500px;}</style> |
||||
|
||||
<script type="application/javascript"> |
||||
function $init() {return true;} |
||||
</script> |
||||
|
||||
<body> |
||||
<p checked class="title" id='title'>Title</p> |
||||
<!-- here goes the rest of the page --> |
||||
</body> |
||||
``` |
||||
|
||||
```javascript |
||||
function $initHighlight(block, cls) { |
||||
try { |
||||
if (cls.search(/\bno\-highlight\b/) != -1) |
||||
return process(block, true, 0x0F) + |
||||
` class="${cls}"`; |
||||
} catch (e) { |
||||
/* handle exception */ |
||||
} |
||||
for (var i = 0 / 2; i < classes.length; i++) { |
||||
if (checkCondition(classes[i]) === undefined) |
||||
console.log('undefined'); |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
<web-component>{block}</web-component> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export $initHighlight; |
||||
``` |
||||
|
||||
```cpp |
||||
#include <iostream> |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
|
||||
/* An annoying "Hello World" example */ |
||||
for (auto i = 0; i < 0xFFFF; i++) |
||||
cout << "Hello, World!" << endl; |
||||
|
||||
char c = '\n'; |
||||
unordered_map <string, vector<string> > m; |
||||
m["key"] = "\\\\"; // this is an error |
||||
|
||||
return -2e3 + 12l; |
||||
} |
||||
``` |
||||
|
||||
```sql |
||||
CREATE TABLE "topic" ( |
||||
"id" serial NOT NULL PRIMARY KEY, |
||||
"forum_id" integer NOT NULL, |
||||
"subject" varchar(255) NOT NULL |
||||
); |
||||
ALTER TABLE "topic" |
||||
ADD CONSTRAINT forum_id FOREIGN KEY ("forum_id") |
||||
REFERENCES "forum" ("id"); |
||||
|
||||
-- Initials |
||||
insert into "topic" ("forum_id", "subject") |
||||
values (2, 'D''artagnian'); |
||||
``` |
||||
|
||||
```objectivec |
||||
#import <UIKit/UIKit.h> |
||||
#import "Dependency.h" |
||||
|
||||
@protocol WorldDataSource |
||||
@optional |
||||
- (NSString*)worldName; |
||||
@required |
||||
- (BOOL)allowsToLive; |
||||
@end |
||||
|
||||
@property (nonatomic, readonly) NSString *title; |
||||
- (IBAction) show; |
||||
@end |
||||
``` |
||||
|
||||
```java |
||||
/** |
||||
* @author John Smith <john.smith@example.com> |
||||
*/ |
||||
package l2f.gameserver.model; |
||||
|
||||
public abstract class L2Char extends L2Object { |
||||
public static final Short ERROR = 0x0001; |
||||
|
||||
public void moveTo(int x, int y, int z) { |
||||
_ai = null; |
||||
log("Should not be called"); |
||||
if (1 > 5) { // wtf!? |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
```swift |
||||
import Foundation |
||||
|
||||
@objc class Person: Entity { |
||||
var name: String! |
||||
var age: Int! |
||||
|
||||
init(name: String, age: Int) { |
||||
/* /* ... */ */ |
||||
} |
||||
|
||||
// Return a descriptive string for this person |
||||
func description(offset: Int = 0) -> String { |
||||
return "\(name) is \(age + offset) years old" |
||||
} |
||||
} |
||||
``` |
||||
|
||||
```css |
||||
@font-face { |
||||
font-family: Chunkfive; src: url('Chunkfive.otf'); |
||||
} |
||||
|
||||
body, .usertext { |
||||
color: #F0F0F0; background: #600; |
||||
font-family: Chunkfive, sans; |
||||
} |
||||
|
||||
@import url(print.css); |
||||
@media print { |
||||
a[href^=http]::after { |
||||
content: attr(href) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
```ruby |
||||
# The Greeter class |
||||
class Greeter |
||||
def initialize(name) |
||||
@name = name.capitalize |
||||
end |
||||
|
||||
def salute |
||||
puts "Hello #{@name}!" |
||||
end |
||||
end |
||||
|
||||
g = Greeter.new("world") |
||||
g.salute |
||||
``` |
||||
|
||||
```makefile |
||||
# Makefile |
||||
|
||||
BUILDDIR = _build |
||||
EXTRAS ?= $(BUILDDIR)/extras |
||||
|
||||
.PHONY: main clean |
||||
|
||||
main: |
||||
@echo "Building main facility..." |
||||
build_main $(BUILDDIR) |
||||
|
||||
clean: |
||||
rm -rf $(BUILDDIR)/* |
||||
``` |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import "fmt" |
||||
|
||||
func main() { |
||||
ch := make(chan float64) |
||||
ch <- 1.0e10 // magic number |
||||
x, ok := <- ch |
||||
defer fmt.Println(`exitting now\`) |
||||
go println(len("hello world!")) |
||||
return |
||||
} |
||||
``` |
||||
|
||||
```bash |
||||
#!/bin/bash |
||||
|
||||
###### CONFIG |
||||
ACCEPTED_HOSTS="/root/.hag_accepted.conf" |
||||
BE_VERBOSE=false |
||||
|
||||
if [ "$UID" -ne 0 ] |
||||
then |
||||
echo "Superuser rights required" |
||||
exit 2 |
||||
fi |
||||
|
||||
genApacheConf(){ |
||||
echo -e "# Host ${HOME_DIR}$1/$2 :" |
||||
} |
||||
``` |
||||
|
||||
```ini |
||||
; boilerplate |
||||
[package] |
||||
name = "some_name" |
||||
authors = ["Author"] |
||||
description = "This is \ |
||||
a description" |
||||
|
||||
[[lib]] |
||||
name = ${NAME} |
||||
default = True |
||||
auto = no |
||||
counter = 1_000 |
||||
``` |
@ -0,0 +1,68 @@ |
||||
title: Elements |
||||
date: 2013-12-24 23:29:08 |
||||
tags: |
||||
--- |
||||
|
||||
The purpose of this post is to help you make sure all of HTML elements can display properly. If you use CSS reset, don't forget to redefine the style by yourself. |
||||
|
||||
--- |
||||
|
||||
# Heading 1 |
||||
|
||||
## Heading 2 |
||||
|
||||
### Heading 3 |
||||
|
||||
#### Heading 4 |
||||
|
||||
##### Heading 5 |
||||
|
||||
###### Heading 6 |
||||
|
||||
--- |
||||
|
||||
## Paragraph |
||||
|
||||
Lorem ipsum dolor sit amet, [test link]() consectetur adipiscing elit. **Strong text** pellentesque ligula commodo viverra vehicula. *Italic text* at ullamcorper enim. Morbi a euismod nibh. <u>Underline text</u> non elit nisl. ~~Deleted text~~ tristique, sem id condimentum tempus, metus lectus venenatis mauris, sit amet semper lorem felis a eros. Fusce egestas nibh at sagittis auctor. Sed ultricies ac arcu quis molestie. Donec dapibus nunc in nibh egestas, vitae volutpat sem iaculis. Curabitur sem tellus, elementum nec quam id, fermentum laoreet mi. Ut mollis ullamcorper turpis, vitae facilisis velit ultricies sit amet. Etiam laoreet dui odio, id tempus justo tincidunt id. Phasellus scelerisque nunc sed nunc ultricies accumsan. |
||||
|
||||
Interdum et malesuada fames ac ante ipsum primis in faucibus. `Sed erat diam`, blandit eget felis aliquam, rhoncus varius urna. Donec tellus sapien, sodales eget ante vitae, feugiat ullamcorper urna. Praesent auctor dui vitae dapibus eleifend. Proin viverra mollis neque, ut ullamcorper elit posuere eget. |
||||
|
||||
> Praesent diam elit, interdum ut pulvinar placerat, imperdiet at magna. |
||||
|
||||
Maecenas ornare arcu at mi suscipit, non molestie tortor ultrices. Aenean convallis, diam et congue ultricies, erat magna tincidunt orci, pulvinar posuere mi sapien ac magna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent vitae placerat mauris. Nullam laoreet ante posuere tortor blandit auctor. Sed id ligula volutpat leo consequat placerat. Mauris fermentum dolor sed augue malesuada sollicitudin. Vivamus ultrices nunc felis, quis viverra orci eleifend ut. Donec et quam id urna cursus posuere. Donec elementum scelerisque laoreet. |
||||
|
||||
## List Types |
||||
|
||||
### Definition List (dl) |
||||
|
||||
<dl><dt>Definition List Title</dt><dd>This is a definition list division.</dd></dl> |
||||
|
||||
### Ordered List (ol) |
||||
|
||||
1. List Item 1 |
||||
2. List Item 2 |
||||
3. List Item 3 |
||||
|
||||
### Unordered List (ul) |
||||
|
||||
- List Item 1 |
||||
- List Item 2 |
||||
- List Item 3 |
||||
|
||||
### Checkbox List (ul) |
||||
|
||||
- [ ] List Item 1 unchecked |
||||
- [x] List Item 2 checked |
||||
- [X] List Item 3 checked |
||||
|
||||
## Table |
||||
|
||||
| Table Header 1 | Table Header 2 | Table Header 3 | |
||||
| - | - | - | |
||||
| Division 1 | Division 2 | Division 3 | |
||||
| Division 1 | Division 2 | Division 3 | |
||||
| Division 1 | Division 2 | Division 3 | |
||||
|
||||
## Misc Stuff - abbr, acronym, sub, sup, kbd, etc. |
||||
|
||||
Lorem <sup>superscript</sup> dolor <sub>subscript</sub> amet, consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. <cite>cite</cite>. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. <acronym title="National Basketball Association">NBA</acronym> Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus. <abbr title="Avenue">AVE</abbr>. Use <kbd>Crtl</kbd> + <kbd>C</kbd> to stop. |
@ -0,0 +1,12 @@ |
||||
title: Excerpts |
||||
date: 2013-12-25 00:23:23 |
||||
tags: |
||||
--- |
||||
|
||||
The following contents should be invisible in home/archive page. |
||||
|
||||
<!-- more --> |
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce eget urna vitae velit eleifend interdum at ac nisi. In nec ligula lacus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eu cursus erat, ut dapibus quam. Aliquam eleifend dolor vitae libero pharetra adipiscing. Etiam adipiscing dolor a quam tempor, eu convallis nulla varius. Aliquam sollicitudin risus a porta aliquam. Ut nec velit dolor. Proin eget leo lobortis, aliquam est sed, mollis mauris. Fusce vitae leo pretium massa accumsan condimentum. Fusce malesuada gravida lectus vel vulputate. Donec bibendum porta nibh ut aliquam. |
||||
|
||||
Sed lorem felis, congue non fringilla eu, aliquam eu eros. Curabitur orci libero, mollis sed semper vitae, adipiscing in lectus. Aenean non egestas odio. Donec sollicitudin nisi quis lorem gravida, in pharetra mauris fringilla. Duis sit amet faucibus dolor, id aliquam neque. In egestas, odio gravida tempor dictum, mauris felis faucibus purus, sit amet commodo lacus diam vitae est. Ut ut quam eget massa semper sodales. Aenean non ipsum cursus, blandit lectus in, ornare odio. Curabitur ultrices porttitor vulputate. |
@ -0,0 +1,19 @@ |
||||
title: Gallery Post |
||||
date: 2013-12-25 00:16:18 |
||||
photos: |
||||
- /assets/wallpaper-2572384.jpg |
||||
- /assets/wallpaper-2311325.jpg |
||||
- /assets/wallpaper-878514.jpg |
||||
- https://via.placeholder.com/350x150.jpg |
||||
--- |
||||
|
||||
This post contains 4 photos: |
||||
|
||||
- Widescreen wallpaper |
||||
- Portrait photo |
||||
- Dual widescreen wallpaper |
||||
- Small photo |
||||
|
||||
All photos should be displayed properly. |
||||
|
||||
*From [Wallbase.cc](http://wallbase.cc)* |
@ -0,0 +1,6 @@ |
||||
title: Hello World |
||||
date: 2013-12-24 17:49:32 |
||||
tags: |
||||
--- |
||||
|
||||
Welcome to [Hexo](http://zespia.tw/hexo)! This is your very first post. Check [documentation](http://zespia.tw/hexo/docs) to learn how to use. |
@ -0,0 +1,514 @@ |
||||
title: HTTP, Servlet, Tomcat |
||||
author: lensfrex |
||||
cover: https://oss-img.ciduid.top/blog/covers/topimage-xb3-pc.jpg |
||||
date: 2023-05-13 15:50:00 |
||||
--- |
||||
# 学习Servlet和Tomcat |
||||
|
||||
## 前置任务:了解学习HTTP |
||||
|
||||
如果你选了网络工程导论这门课,并且已经了解了HTTP相关的知识,则可以跳过本节,直接开主线任务:[传送点](#主线任务:Servlet,Tomcat),但是也可以重新开始学习这部分的内容。 |
||||
|
||||
想了解更多:[HTTP | MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTTP) |
||||
|
||||
### HTTP介绍 |
||||
|
||||
超文本传输协议(HyperText Transfer Protocol,HTTP)是一个应用层协议。所谓协议,就是数据传输格式的一个约定。 |
||||
|
||||
HTTP最初设计是用来传输html页面这种纯文本数据的,但是实际上只要声明好header,传输什么都是可以的,图片,文件等等,或者也可以摇身一变,变成另一个协议(grpc, 基于http2)来使用,总之,http其实是非常灵活的。 |
||||
|
||||
### HTTP消息 |
||||
|
||||
一个规范的,完整的HTTP消息结构是这样的: |
||||
|
||||
请求消息(GET方法): |
||||
地址:`https://github.com` |
||||
|
||||
```http |
||||
GET / HTTP/1.1 |
||||
Host: github.com |
||||
authority: github.com |
||||
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 |
||||
accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 |
||||
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35 |
||||
content-length: 0 |
||||
|
||||
|
||||
``` |
||||
|
||||
请求消息(POST方法,带请求体): |
||||
|
||||
地址:`http://bkjx.wust.edu.cn/Logon.do?method=logon` |
||||
|
||||
```http |
||||
POST /Logon.do?method=logon HTTP/1.1 |
||||
Host: bkjx.wust.edu.cn |
||||
Proxy-Connection: Keep-Alive |
||||
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 |
||||
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 |
||||
Cache-Control: no-cache |
||||
Connection: keep-alive |
||||
Content-Type: application/x-www-form-urlencoded |
||||
Cookie: bzb_njw=303511D07EF501147333F6B099D16CB9; SERVERID=122 |
||||
Origin: http://bkjx.wust.edu.cn |
||||
Pragma: no-cache |
||||
Referer: http://bkjx.wust.edu.cn/ |
||||
Upgrade-Insecure-Requests: 1 |
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35 |
||||
Content-Length: 94 |
||||
|
||||
userAccount=&userPassWorld=&encoded=dMcs2P8fNag0n1d03f0UVgu02^%^257F^%^25QF^%^25pcs0Ygd4fbgHs69 |
||||
``` |
||||
|
||||
|
||||
响应消息(header内容精简过): |
||||
|
||||
```http |
||||
HTTP/1.1 200 OK |
||||
Server: GitHub.com |
||||
Date: Sat, 13 May 2023 02:59:34 GMT |
||||
Content-Type: text/html; charset=utf-8 |
||||
Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Language, Accept-Encoding, Accept, X-Requested-With |
||||
content-language: en-US |
||||
ETag: W/"bfeda72459b363617514a4f13882cece" |
||||
Set-Cookie: _octo=GH1.1.496438640.1683116779; Path=/; Domain=github.com; Expires=Mon, 13 May 2024 02:59:39 GMT; Secure; SameSite=Lax |
||||
Accept-Ranges: bytes |
||||
Transfer-Encoding: chunked |
||||
|
||||
<!DOCTYPE html> |
||||
<html lang="en" data-a11y-animated-images="system"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<link rel="dns-prefetch" href="https://github.githubassets.com"> |
||||
...(还有一堆的网页内容) |
||||
``` |
||||
|
||||
请求和响应的消息体长得还挺像的,估计大家都能看出来了。 |
||||
|
||||
#### 第一行 -『起始行』与『状态行』 |
||||
|
||||
http请求消息中,第一行为『起始行』: |
||||
- `GET / HTTP/1.1` |
||||
- `GET /background.png HTTP/1.0` |
||||
- `POST /Logon.do?method=logon HTTP/1.1` |
||||
|
||||
起始行分为三个部分,分别定义了请求方法,请求目标和当前请求使用的http协议版本 |
||||
|
||||
第一个部分为『[请求方法](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods)』,一般有GET, POST, OPTIONS, PUT等好多种,但是实际使用几乎就只有GET和POST两种。 |
||||
|
||||
随后紧接着的部分是『请求目标』,在普通的http请求中也可以简单理解为请求路径,也就是咱们看到的浏览器地址后面的部分(xxx.com/abcd/efg中的/abcd/efg,bkjx.wust.edu.cn/Logon.do?method=logon中的/Logon.do?method=logon部分) |
||||
|
||||
在部分场合下,请求目标有时也是一个绝对路径的URL,如当使用HTTP代理访问网站时,浏览器向代理服务器(如软件)发送的数据也是一个http请求,只不过真实的请求放在了body部分,代理程序只负责原样发送数据:`CONNECT google.com:443 HTTP/1.1` |
||||
|
||||
这里就抄一下MDN的介绍: |
||||
|
||||
> - 一个绝对路径,末尾跟上一个 '?' 和查询字符串。这是最常见的形式,称为原始形式(origin form),被 GET、POST、HEAD 和 OPTIONS 方法所使用。 |
||||
> - - POST / HTTP/1.1 |
||||
> - - GET /background.png HTTP/1.0 |
||||
> - - HEAD /test.html?query=alibaba HTTP/1.1 |
||||
> - - OPTIONS /anypage.html HTTP/1.0 |
||||
> - 一个完整的 URL,被称为绝对形式(absolute form),主要在使用 GET 方法连接到代理时使用。GET http://developer.mozilla.org/en-US/docs/Web/HTTP/Messages HTTP/1.1 |
||||
> - 由域名和可选端口(以 ':' 为前缀)组成的 URL 的 authority 部分,称为 authority form。仅在使用 CONNECT 建立 HTTP 隧道时才使用。CONNECT developer.mozilla.org:80 HTTP/1.1 |
||||
> - 星号形式(asterisk form),一个简单的星号('*'),配合 OPTIONS 方法使用,代表整个服务器。OPTIONS * HTTP/1.1 |
||||
|
||||
最后的一部分为『HTTP版本』,声明了这次请求使用的http版本。目前常用的版本有`HTTP/1.1`,`HTTP/2`,`HTTP/3`。 |
||||
|
||||
> 另外,和其他HTTP版本不同的是,HTTP/3版本是在基于UDP的QUIC协议之上实现传输的,QUIC是与TCP和UDP等同级(传输层)的协议,而其他的HTTP版本都是在TCP协议上实现的, |
||||
|
||||
对于响应消息,第一行为『状态行』。 |
||||
- `HTTP/1.1 200 OK` |
||||
- `HTTP/1.1 404 Not Found` |
||||
- `HTTP/1.1 500 Internal Server Error` **(心 脏 骤 停)** |
||||
|
||||
也是三个部分。 |
||||
|
||||
响应的状态行就挺简单的了。 |
||||
|
||||
第一个就不用说了。 |
||||
|
||||
第二个则为『状态码』,就是咱们常见的404, 200这些,表明了服务端处理请求的结果状态。 |
||||
|
||||
第三个部分则为『状态文本』,其实就是状态码的描述。 |
||||
|
||||
有哪些状态码,可以[自行了解了解](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status),最好记得一些常用的状态码的含义,要是乱抛状态码给前端有的是一顿毒打( |
||||
|
||||
当然,在许多场合中,业务状态(成功,认证错误,参数错误等等)一般并不通过状态码表示,而是200响应,在响应消息体里边体现错误;但是也有状态码和消息体都体现的情况。具体还是要跟前端和客户端的小伙伴商量约定好规范和文档,按照团队的规范和文档行事,能少很多不必要的麻烦。 |
||||
|
||||
#### 『标头』(Header) |
||||
|
||||
请求和响应的消息体过了第一行之后,就到了『标头』(header)部分了,一般咱们把这玩意叫『请求头』和『响应头』。 |
||||
|
||||
http消息的header实际上就是一个键值对,格式为`Key: Value`,一行一个 |
||||
|
||||
需要注意的是,这里的key是不分大小写的,例如`Host`,`host`,`HOST`,`hOST`都是同一个东西。 |
||||
|
||||
同一个key的Header也可以有多个值,因此,在各种http库中,获取到的header值都是一个List<String>。 |
||||
|
||||
比如: |
||||
``` |
||||
Set-Cookie: logged_in=no; Path=/; Domain=github.com; Expires=Mon, 13 May 2024 02:59:39 GMT; HttpOnly; Secure; SameSite=Lax |
||||
Set-Cookie: access_token=abcdefg; Path=/; Domain=github.com; Expires=Mon, 13 May 2024 02:59:39 GMT; Secure; SameSite=Lax |
||||
``` |
||||
|
||||
虽然如此,但是除非有必要(例如上面的例子中给客户端返回Cookie),在响应或者请求的时候最好不要这么干,即使可以这么干,否则在对方处理的时候会很麻烦。 |
||||
|
||||
一般来说,咱们会经常见到这些请求头或者响应头: |
||||
> - `Host`: 请求的目标主机,通常是在同一个ip和端口上有多个不同的服务时,提供给nginx或者apache等类似的其他的反代网关判断究竟应该执行哪个配置块时使用的。 |
||||
> - `User-Agent`: 表明请求客户端信息,一般格式为`程序名/版本`,当然,也可以添加其他额外的信息,如firefox的ua:`Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0` |
||||
> - `Content-Type`: 请求/响应传输数据的类型,即下一节中body部分数据的格式。按照规范,这个字段的值应该使用MIME type格式,这里是一些[常用的MIME类型](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)。 |
||||
> - `Accept`: 能接受的数据格式,一般为用逗号`,`分隔开的若干个MIME type格式 |
||||
> - `Accept-Encoding`: 指定能够接受的响应数据编码,和`Accept`不同的是,`Accept`指定的是body部分的数据格式,但是这里指定的是整个响应的数据编码,通常是压缩算法,如`gzip, deflate, br`,设置了这个值后,如果服务端支持,响应的时候会对整个HTTP消息进行相应的压缩后,再返回响应给客户端,客户端需要进行解压解码后再进行处理。一般http框架和web框架都能自动进行自动的处理。 |
||||
> - `Referer`: 说明请求是从哪里来的 |
||||
> - `Cookie`: 向服务器请求时附带的一些信息,就是咱们常说的cookie,浏览器在请求的时候如果有相对应的Cookie,会自动附带上这些Cookie进行请求。 |
||||
> - `Set-Cookie`: 服务端响应回来的Cookie,浏览器看到这个header之后就会根据这个header的值去保存cookie到浏览器里边。下次请求cookie对应规则的地址时会自动附带这些信息,即上一条中的Cookie字段。 |
||||
|
||||
Cookie一般用于用户的识别和身份认证以及一些额外信息的保存。[了解更多关于Cookie的东西](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies) |
||||
|
||||
实际上,很多header值只是作为一个参考,作为后端开发,不应该过于信赖前端/客户端发来的数据,后端程序必须要对前端/客户端做充足的数据校验,如`Content-Type`,前端/客户端可以在请求的时候在`Content-Type`字段里说他发了`image/png`图片,但是可能实际上发来的数据是一个程序,如果不做任何校验直接保存到服务器上,那可就麻烦大了。 |
||||
|
||||
一般来说,很多http请求和web服务框架都能帮咱们写好这些通用的请求头和响应头,不用咱们过于操心,当然,如果有需求更改的话也能更改好。 |
||||
|
||||
这里对header的作用描述不一定十分的准确,可以查看相关的文档了解详细详情:[HTTP 标头(header)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers),Ctrl+F直接一搜就到 |
||||
|
||||
#### 『主体』(body) |
||||
|
||||
在前面的header定义完之后,需要各一行空行,才到http消息中的『主体』部分。 |
||||
|
||||
这个body部分就是这个请求真正想要传输的数据。 |
||||
|
||||
这部分可以是纯文本,也可以是二进制,都行,只要Content-Type说好了就行,没说的话,一般也行,但是得看后端处不处理了,一些后端框架不会帮你自动判断Body格式,或者不同的Content-Type处理的方式不同,如果请求的时候没有设置对,可能会有些麻烦事。 |
||||
|
||||
> 另外,上面的起始行/状态行和header行以及body和上面几行中间间隔的空行,和windows一样,使用的换行符都为CRLF,也就是`\r\n`,而不是linux/unix常用的`\n`。当然,这个知道就好了,库会帮咱们处理好的。 |
||||
|
||||
### HTTPS |
||||
|
||||
HTTPS就是加密后的HTTP,'S'就是"Secure"。一般使用TLS来加密。原始的http消息经过全部加密后,再进行传输。只要中间偷听的人拿不到密钥,就不可能知道除了请求的ip以外任何的http信息。 |
||||
|
||||
关于现代TLS加密的机制,可以去看看[这篇文章](https://zhuanlan.zhihu.com/p/43789231) |
||||
|
||||
一般约定http跑在80端口上,而https跑在443端口上,但是也可以用其他端口。 |
||||
|
||||
我的建议是,https能上就上。 |
||||
|
||||
## 主线任务:Servlet,Tomcat |
||||
|
||||
### Servlet,介绍 |
||||
|
||||
在开始Tomcat的学习之前,咱们先来了解Servlet。 |
||||
|
||||
很多教程里都有讲过,就是运行在服务端的applet,也就是『Server Applet』。 |
||||
|
||||
说实话,当我听到“Applet”的时候...印象里貌似已经是上世纪早已失传的老东西了...但是其实现在咱们说的的“Server Applet”和那个客户端的“Applet”不是同一个东西,虽然都是当年就有的东西。 |
||||
|
||||
Servlet实际上就是一个接口。 |
||||
|
||||
咱们先来点开Servlet(清掉了注释): |
||||
|
||||
![Servlet.java](https://oss-img.ciduid.top/backend_tutorial/5.14/res/servlet.png "Servlet.java") |
||||
|
||||
```java |
||||
package javax.servlet; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
public interface Servlet { |
||||
|
||||
public void init(ServletConfig config) throws ServletException; |
||||
|
||||
public ServletConfig getServletConfig(); |
||||
|
||||
public void service(ServletRequest req, ServletResponse res) |
||||
throws ServletException, IOException; |
||||
|
||||
public String getServletInfo(); |
||||
|
||||
public void destroy(); |
||||
} |
||||
``` |
||||
|
||||
是的,整个Servlet接口就这些代码。 |
||||
|
||||
那Servlet是干啥的? |
||||
|
||||
大伙想想接口(interface)是用来干什么的?当然是一种规范约定。Servlet就是一种规范,所有实现Servlet的类,也就是处理网络请求的类要做什么。你要是不想按照Servlet来写,也是可以的,但是会少了很多库和框架的支持,很多东西就要你手动实现了。 |
||||
|
||||
初始化`init(ServletConfig config)`该做什么,接收到请求`service(ServletRequest req, ServletResponse res)`该做什么,服务器要退出了`destroy()`又该做什么,这些Servlet都不负责帮你实现,都需要你自己去实现。 |
||||
|
||||
这里的Servlet只是非常底层的Servlet定义,实际上,咱们学Servlet,其实就是学HttpServlet,学HttpServlet来实现一些非常简单的Web响应处理;咱们说的Servlet,其实就是实现了HttpServlet的自己写的类。 |
||||
|
||||
![HttpServlet.java](https://oss-img.ciduid.top/backend_tutorial/5.14/res/HttpServlet.png "HttpServlet.java") |
||||
|
||||
HttpServlet根据Http对最原始的Servlet又封装了一层,也就是专门针对Http的Servlet。可以看到,相比于原来的几个方法,多了很多和http处理相关的方法。 |
||||
|
||||
HttpServlet最核心的方法就是`doGet(HttpServletRequest req, HttpServletResponse resp)`, `doPost(HttpServletRequest req, HttpServletResponse resp)`等等这种`doXxx`的方法,实际上就是对应请求中`GET`, `POST`等等请求的处理,当服务接受到这些请求的时候,就进入对应的方法处理,从req读请求,再向resp写响应。 |
||||
|
||||
恭喜你,当你接触到这里的时候,就已经到了开始入门写Web程序的阶段了,你现在在做的事就是十几年前的服务端程序员在干的事了,当年web服务还不复杂的时候,很多时候就是拿Servlet写页面渲染来实现web服务。 |
||||
|
||||
当然,只是实现了Servlet并不代表就是一个能跑的服务端程序了。是,我们是写了Servlet,但是Socket那些IO相关的东西咱还没有呢。 |
||||
|
||||
Servlet并不负责和客户端直接沟通,也就是说,那些Socket侦听8080啥的底层IO处理并不是在Servlet里面,实际的连接处理那是『Tomcat』那家伙来负责的。 |
||||
|
||||
### Tomcat,介绍 |
||||
|
||||
Tomcat是一个『Servlet容器』,也就是说,这玩意是个罐子,用来装一个个的Servlet程序。 |
||||
|
||||
![Tomcat](https://oss-img.ciduid.top/backend_tutorial/5.14/res/tomcat.png "Tomcat") |
||||
|
||||
上文说到过,咱们虽然写了Servlet,但是没人负责和客户端建立连接沟通,所以,这个事就是Tomcat来做的了。 |
||||
|
||||
要知道,解析原始Http消息是一件非常麻烦的事情,光是Http标准那几百页有的是你看的了,你要干吗?反正我是不会自己干的,更何况还有各种IO相关的脏活累活要干。但是咱们的大好人——Tomcat,这些都让它来负责了。 |
||||
|
||||
Tomcat侦听着端口,客户端向我们这边发来了一个请求,Tomcat对客户端发来的Http请求进行解析,解析出请求路径,方法,header之类的信息,根据这些信息,把请求小心翼翼地包装好,封装成HttpServlettRequest,交给对应的Servlet去处理,然后Servlet处理完之后就返回HttpServletResponse给Tomcat,Tomcat就把这个响应又写成http消息,交给客户端。一个通过Servlet实现的服务端程序处理一个请求地过程就这么完成了。 |
||||
|
||||
### Servlet,开干! |
||||
|
||||
说了这么多,估计大家手已经开始痒了。那就咱们现在就来开始写几个简单的HttpServlet玩玩! |
||||
|
||||
#### 梦的开始,Hello World |
||||
|
||||
计算机最经典的一句名言,莫过于那一声响彻云霄的 ***『Hello World!』*** |
||||
|
||||
现在,就来写个Servlet,当用浏览器访问的时候,能返回文本“Hello World!” |
||||
|
||||
咱先来建一个简单的工程,什么框架都不用加,这样就好了嗼 |
||||
|
||||
![新建工程](https://oss-img.ciduid.top/backend_tutorial/5.14/res/new_project.png "新建工程") |
||||
|
||||
很多JDK的发行版都没有带Servlet,因此如果写代码的时候没有Servlet和HttpServlet类,需要自己手动在Maven中引入依赖: |
||||
|
||||
```xml |
||||
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api --> |
||||
<dependency> |
||||
<groupId>jakarta.servlet</groupId> |
||||
<artifactId>jakarta.servlet-api</artifactId> |
||||
<version>6.0.0</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
``` |
||||
|
||||
注意,上面引用的是`jakarta.servlet`,因为19年oracle把javax送了出去,但是又不准继续使用javax这个名字,所以jdk11以后的版本用的都是jakarta作为包名,因此需要将名称改成jakarta。 |
||||
|
||||
也就是说,如果你还在用java 11以下的jdk,需要引入`javax.servlet`的servlet接口。 |
||||
|
||||
```xml |
||||
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> |
||||
<dependency> |
||||
<groupId>javax.servlet</groupId> |
||||
<artifactId>javax.servlet-api</artifactId> |
||||
<version>4.0.1</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
``` |
||||
|
||||
这些完事之后,就可以开撸了。 |
||||
|
||||
1. 继承HttpServlet类 |
||||
前文说过,要实现Servlet,就需要实现相应的方法。但是咱不可能又要从头写起,因此咱们继承的是HttpServlet类,而不是直接实现Servlet接口。 |
||||
|
||||
```java |
||||
import jakarta.servlet.http.HttpServlet; |
||||
|
||||
public class HelloWorld extends HttpServlet { |
||||
|
||||
} |
||||
``` |
||||
|
||||
这个时候,就可以去重写HttpServlet相应的`doXxx(HttpServletRequest req, HttpServletResponse resp)`方法来处理不同的Http请求了。 |
||||
|
||||
2. 重写`doXxx(HttpServletRequest req, HttpServletResponse resp)` |
||||
|
||||
> idea每日小技巧🥰 |
||||
|
||||
简单,idea里边直接`Alt+Insert`,选择`Override Methods` |
||||
|
||||
![Alt+Insert](https://oss-img.ciduid.top/backend_tutorial/5.14/res/insert.png "这只是一张图片") |
||||
|
||||
可以看到,有很多可以重写的方法: |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/override.png "可以重写的方法") |
||||
|
||||
咱们这里选择`doGet()`这项就好了。 |
||||
|
||||
选好之后,嘣,idea就给我们生成好了重写方法了。 |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/override_2.png "可以重写的方法") |
||||
|
||||
记得删除原来的`super.doGet(req, resp);` |
||||
|
||||
> 我就不删,咋的 |
||||
> 可能会导致服务端直接返回`400`或者`405`方法不支持错误给客户端,按住ctrl点开super的`doGet`就能看到了: |
||||
> ![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/super.png "super") |
||||
|
||||
往`doGet`方法添加(粘贴)这些内容: |
||||
```java |
||||
PrintWriter writer = resp.getWriter(); |
||||
writer.print("Hello World!"); |
||||
writer.flush(); |
||||
``` |
||||
|
||||
这样,一个直接向客户端(请求者)返回“Hello World!”字符串的Servlet就这样写完了,当下文Tomcat部署好之后,浏览器直接访问相应的页面,就能看到一个Hello World出现在你的浏览器屏幕。 |
||||
|
||||
就这? |
||||
|
||||
是的。因为这确实就这么简单,但是当咱们业务复杂起来之后,那可就有得你写了。不过,那是后话了,咱们现在学的是原理,诚然框架非常好使,刷刷刷就写完了,但是总归要知道为什么这样,否则只是*只知其然,而不知其所以然*。 |
||||
|
||||
欸欸欸?不是说好要跑起来吗? |
||||
|
||||
别急,咱还没搞好Tomcat呢 |
||||
|
||||
#### 梦的第二步,Tomcat |
||||
|
||||
前面说到过,要让服务程序真正跑起来,还得Tomcat来干那些IO的脏活累活。 |
||||
|
||||
首先,得去[Tomcat的官网](https://tomcat.apache.org/)下载好Tomcat. |
||||
|
||||
注意,Tomcat 10版本和9版本在Servlet的包路径上有很大的不同,原因同上,还是Oracle那家伙搞的麻烦... |
||||
|
||||
如果你用的是`javax.servlet`请使用[Tomcat 9版本](https://tomcat.apache.org/download-90.cgi);如果使用的是`jakarta.servlet`,请使用[Tomcat 10](https://tomcat.apache.org/download-10.cgi)。 |
||||
|
||||
下载完后,你能得到`apache-tomcat-10.1.8-windows-x64.zip`这样的压缩包,把它解压到你喜欢的地方。 |
||||
|
||||
1. 配置 |
||||
接下来,又是喜闻乐见的环境配置环节了🥰 |
||||
|
||||
在系统变量中,添加`CATALINA_HOME`和`CATALINA_BASE`两个环境变量,指向你刚刚解压的Tomcat路径,如我这里解压到了`D:\apache-tomcat-10.1.8`,环境变量的值就填`D:\apache-tomcat-10.1.8` |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/location.png) |
||||
|
||||
配置好之后,直接运行bin里边的`startup.bat`在启动tomcat。 |
||||
|
||||
如果控制台中文乱码了,可以修改conf下的`logging.properties`文件,将里边的`utf-8`替换为`936`,保存之后再启动,就好了 |
||||
|
||||
如果一切成功,控制台的输出应该是像这样的: |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/tomcat_console.png) |
||||
|
||||
咱来conf文件夹瞧瞧。 |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/conf.png) |
||||
|
||||
可以看到,有`server.xml`, `web.xml`和`content.xml`这三个配置文件。 |
||||
|
||||
*麻了,怎么又是XML...* |
||||
|
||||
咱一个一个来看 |
||||
|
||||
`server.xml`负责配置服务器相关的配置参数,如监听端口,最大连接数等等最底层的IO配置。 |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/server.xml.png) |
||||
|
||||
`context.xml`配置的是servlet内容的路径等,tomcat服务器会定时去扫描这个文件。一旦发现文件被修改,就会自动重新加载这个文件,而不需要重启服务器。 |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/content.xml.png) |
||||
|
||||
`web.xml`这家伙可就大了。不管虽然说是大,但其实不是很大,主要都是注解之类的。 |
||||
|
||||
`web.xml`基本上可以不用动,只需要在咱们的Servlet类中用上`@WebServlet`注解,Tomcat就能好心帮我们挂载上去了。 |
||||
|
||||
当然,如果你想自己手动写`web.xml`部署,也可以自己去了解了解,相信我,了解完之后你肯定会回来用注解的。 |
||||
|
||||
打开你的浏览器,访问`http://127.0.0.1:8080`,如果你在`server.xml`里设置的端口不是`8080`,请以你的端口设置为准,如果一切正常,可以看到Tomcat默认的管理页面: |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/tomcat_index.png) |
||||
|
||||
2. 做点小修改 |
||||
为了让Web部署更方便,咱们用注解来指定部署路径,用『Maven』来打包成war包进行部署。 |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/web_servlet.png) |
||||
|
||||
图中加了个`@WebServlet("/yahaha")`,表明这个Servlet的路径是`/yahaha`,当访问到这个路径的时候,Tomcat就会去就调用我们写的这个Servlet。 |
||||
|
||||
接着,为了让我们亲爱的的Maven把项目打包成war文件,向项目的`pom.xml`的添加以下内容: |
||||
|
||||
```xml |
||||
<packaging>war</packaging> |
||||
<build> |
||||
<finalName>learn</finalName> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-war-plugin</artifactId> |
||||
<version>3.3.2</version> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
``` |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/pom.png) |
||||
|
||||
好了之后,用maven打包,就得到了咱们想要的war文件了。 |
||||
|
||||
3. 放置War包 |
||||
把刚刚得到的war包拿出来(learn.war)。不难注意到,我们在`pom.xml`中,用`<finalName>learn</finalName>`指定了最后打包输出的文件名为`learn.war`,只要你喜欢,起什么名字都好。 |
||||
|
||||
把它放到tomcat的`webapps`文件夹里。如果没有启动Tomcat,就把它启动起来。如果你已经把tomcat开起来了,放到`webapps`文件夹下面之后,就能看到这么一行日志: |
||||
|
||||
```log |
||||
14-May-2023 09:13:32.437 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR 正在部署web应用程序存档文件[D:\apache-tomcat-10.1.8\webapps\learn.war] |
||||
14-May-2023 09:13:32.465 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR web应用程序存档文件[D:\apache-tomcat-10.1.8\webapps\learn.war]的部署已在[27]ms内完成 |
||||
``` |
||||
|
||||
同时也能看到`webapps`文件夹下面多出了个`learn`,Tomcat帮我们解压好了war包。 |
||||
|
||||
4. 让我看看! |
||||
打开你的浏览器,访问`http://127.0.0.1:8080/learn/yahaha`,就能看到咱们写的Servlet输出了那令人激动的`Hello World!`了! |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hello_world.png) |
||||
|
||||
注意,我们刚刚写的Servlet路径地址并不是`http://127.0.0.1:8080/yahaha`,而是`http://127.0.0.1:8080/应用名/servlet路径`,这里的应用名,就是咱们war包的名字。如果你想从根路径开始,可以把原来`webapps`里的`ROOT`文件夹删掉,也就是前面咱们看到的那个管理页面,然后将刚刚得到的`learn.war`该名成`ROOT.war`,然后像刚刚那样放到`webapps`文件夹下,咱们的这个war就会被部署挂载到根路径`/`下了,此时咱们写的Servlet路径就是`/yahaha`了。 |
||||
|
||||
好了,这个就是你写的第一个Servlet了。能坚持走到这里,或许非常的不容易,看到这个`Hello World!`,或许可能会心中一股激动,这可是你写的第一个Web程序,这是从你的程序里,经过重重关卡发送到浏览器展示出来的文字,不亚于当年前辈们在地球上听到《东方红》时候的激动,指不定还会热泪盈眶,感慨万分;这么一行文字,可能倾注了你一天,或者好几天的时间和经历,但是这都没有关系,最终咱们还是做出来了,这是你自己第一次写出来的真正的Web程序,你这个时候,你已经正式踏上了后端开发的满满长路了。 |
||||
|
||||
好了,先不发电了。 |
||||
|
||||
#### 意犹未尽,继续玩 |
||||
|
||||
就输出一个Hello World!怎么够?咱要玩大的! |
||||
|
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/HttpServletRequest.png) |
||||
|
||||
HttpServlet下有很多方法可以用,获取到的都是些Http参数,这些就是Tomcat帮我们解析好的,直接拿来就用。 |
||||
|
||||
例如,通过`getParameter(String name)`就能拿到请求参数,如`/yahaha?id=1234&type=none`中的`id`和`type`,可以通过`req.getParameter("id")`和`req.getParameter("type")`拿到相应的值,如果没有这个参数,获取到的就是`null`。这个看文档一看就知道。 |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/doc.png) |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/doc_cn.png) |
||||
|
||||
下面,咱们就来改写一下前面的Hello World,让他能够根据提供的参数,来输出不同的内容。 |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hellox.png) |
||||
|
||||
像这样,通过`String id = req.getParameter("id");`拿到参数中的id,然后再在下面输出出来。 |
||||
|
||||
重新打包部署好,访问之前的地址:`http://127.0.0.1:8080/learn/yahaha` |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hello_null.png) |
||||
|
||||
啊,输出了`Hello null!`。这是因为咱们还没给参数呢,当然是null。 |
||||
|
||||
没给,那就给嘛。`http://127.0.0.1:8080/learn/yahaha?id=lensfrex` |
||||
![](https://oss-img.ciduid.top/backend_tutorial/5.14/res/hello_lensfrex.png) |
||||
|
||||
这次咱给了id参数进去,可以看到,hello的内容已经不是null了,而是咱们提供的参数了。你喜欢的话,可以换成其他的参数,访问之后就能看到响应了不同的内容。 |
||||
|
||||
就这样,咱们的服务不是死的了,能根据请求的不同发生变化了。 |
||||
|
||||
到了这里,最基本的Servlet和Tomcat,大家应该是已经知道了。剩下的,就是去了解`HttpServletRequest`和`HttpServletResponse`的API用法了。这些文档就有很多的解释,也可以到处去看看教程,有很多实例可以学习学习。 |
||||
|
||||
### After Story |
||||
|
||||
这里带大家了解了http, Servlet以及Tomcat,看起来好像很麻烦的样子。是的,就是很麻烦,但是上古程序员就是这么走来的。 |
||||
|
||||
现在咱们有了『SpringBoot』以及『Spring』一系列的框架,这玩意可以说是Java能如此流行的最重要的原因。当然,没有Spring,也会有Summer,也会有Winter。SpringBoot Web已经给我们内置了一个Tomcat,而且帮我们自动配置好了一大堆东西,这些在下一部分中陈姐会带你们领略领略。当你学完Spring的时候,就能知道这真的是个非常牛的玩意。 |
||||
|
||||
SpringBoot Web诚然用的非常爽,但是还是不要忘了本,不论上层怎么玩,还是要知道这玩意最基本的东西是怎么一回事的,Servlet还是要了解知道一些的,万变不离其宗,Spring再牛,最基础的还是这些。 |
||||
|
||||
当你玩转SpringBoot的web之后,再来回头看看的时候,哈,原来是这么一回事。 |
||||
|
||||
另外,Servlet容器不只是有Tomcat,他的同事还有`Jetty`, `GlassFish`, `Undertow`等等,只不过Tomcat因为SpringBoot Web默认内置,因此用的也是最多的,如果你喜欢,或者有需求,也可以换成其他的Servlet容器。 |
||||
|
||||
## DLC - 课外读物 |
||||
|
||||
[Servlet入门](https://www.liaoxuefeng.com/wiki/1252599548343744/1304265949708322) |
||||
|
||||
[HTTP | MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTTP) |
||||
|
||||
[servlet的本质是什么,它是如何工作的? - bravo1988的回答](https://www.zhihu.com/question/21416727/answer/690289895) |
||||
|
||||
[servlet的本质是什么,它是如何工作的? - 柳树的回答](https://www.zhihu.com/question/21416727/answer/339012081) |
||||
|
||||
[Servlet到底是什么](http://c.biancheng.net/servlet2/what-is-servlet.html) |
||||
|
||||
[一些Servlet应用的教程](https://www.runoob.com/servlet/servlet-form-data.html) |
@ -0,0 +1,13 @@ |
||||
title: Images |
||||
date: 2013-12-26 22:46:49 |
||||
--- |
||||
|
||||
This is a image test post. |
||||
|
||||
![](/assets/wallpaper-2572384.jpg) |
||||
|
||||
![Caption](/assets/wallpaper-2311325.jpg) |
||||
|
||||
![](/assets/wallpaper-878514.jpg) |
||||
|
||||
![Small Picture](https://via.placeholder.com/350x150.jpg) |
@ -0,0 +1,45 @@ |
||||
title: 给linux网卡上ipv6 |
||||
author: lenfrex |
||||
cover: https://oss-img.ciduid.top/blog/covers/cover(15).png |
||||
tags: |
||||
- ipv6 |
||||
- 折腾 |
||||
- 网络 |
||||
- 服务器 |
||||
categories: |
||||
- 折腾 |
||||
date: 2022-03-20 21:26:00 |
||||
--- |
||||
按照阿里云官网的文档ipv6分配地址并且开通ipv6公网带宽之后,在我本地这里去ping服务器的ipv6地址ping通了,而且延迟比ipv4地址短了将近一半; |
||||
|
||||
有意思的是,当我从服务器ping服务器自己的ipv6地址时,却ping不通,提示`Destination unreachable: Address unreachable` |
||||
|
||||
奇怪了,我的服务器类型是支持ipv6的,而且公网能ping通,偏偏自己ping不通自己...没道理呀... |
||||
|
||||
ipv6本地回环倒是没问题,ping其他纯ipv6网站也是长时间没结果 |
||||
|
||||
去问阿里云客服,给的回复是防火墙icmp没开放,去开了之后也是一样的结果。 |
||||
|
||||
在阿里云官方的[这个文档](https://help.aliyun.com/document_detail/187463.html),图里eth0网卡中有两个ipv6地址,一个对应的应该是私有地址,一个是公有地址,人家分配的公网地址和其中一个地址是对应的,但是我这只有本地的私有地址,刚刚分配到的地址不见显示。 |
||||
|
||||
![](https://oss-img.ciduid.top/aether/Snipaste_2022-03-20_22-21-45.png) |
||||
|
||||
按照一些教程的说法,编辑`/etc/network/interfaces`,还有去`/etc/sysctl.conf`把ipv6给开了,但是都没用。 |
||||
|
||||
后来搜索`ubuntu ipv6 eth0`,找到了[这篇文章](https://www.cnblogs.com/shipfi/archive/2008/01/29/1057950.html),手动把ipv6地址给挂上了网卡,命令如下(root权限): |
||||
|
||||
> ip -6 addr add <ipv6地址>/<prefixlength前缀长度> dev <网卡设备,如eth0> |
||||
|
||||
例如像我这里就是 |
||||
|
||||
> sudo ip -6 addr add 2408:4005:3c2:fe9:3b13:f597:331a:b090/64 dev eth0 |
||||
|
||||
执行完毕之后,用ifconfig一看,eth0果然多了一个对应着分配的ipv6地址。 |
||||
|
||||
![](https://oss-img.ciduid.top/aether/Snipaste_2022-03-20_22-13-50.png) |
||||
|
||||
去ping其他ipv6网站,也能ping通了,至此是可以了。 |
||||
|
||||
记得防火墙ipv4的规则复制一遍改成ipv6的,两种规则是不一样的,需要单独设定规则。 |
||||
|
||||
最后,这是这个破站的ipv6访问地址:https://ipv6.ciduid.top/ |
@ -0,0 +1,5 @@ |
||||
date: 2013-12-24 23:44:13 |
||||
link: https://www.google.com/ |
||||
--- |
||||
|
||||
This is a link post without a title. The title should be the link with or without protocol. Clicking on the link should open [Google](https://www.google.com/) in a new tab or window. |
@ -0,0 +1,6 @@ |
||||
title: Link Post |
||||
date: 2013-12-24 23:30:04 |
||||
link: https://www.google.com/ |
||||
--- |
||||
|
||||
This is a link post. Clicking on the link should open [Google](https://www.google.com/) in a new tab or window. |
@ -0,0 +1,6 @@ |
||||
title: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam justo turpis, tincidunt ac convallis id. |
||||
date: 2013-12-24 23:31:06 |
||||
tags: |
||||
--- |
||||
|
||||
This post has a long title. Make sure the title displayed right. |
@ -0,0 +1,390 @@ |
||||
title: Markdown Style test |
||||
date: 2018-7-24 23:31:06 |
||||
tags: |
||||
- Foo |
||||
- Bar |
||||
--- |
||||
|
||||
This post is originated from [here](https://gist.github.com/apackeer/4159268) and is used for testing markdown style. This post contains nearly every markdown usage. Make sure all the markdown elements below show up correctly. |
||||
|
||||
<!-- more --> |
||||
|
||||
------- |
||||
|
||||
## Headers |
||||
|
||||
```markdown |
||||
# H1 |
||||
## H2 |
||||
### H3 |
||||
#### H4 |
||||
##### H5 |
||||
###### H6 |
||||
|
||||
Alternatively, for H1 and H2, an underline-ish style: |
||||
|
||||
Alt-H1 |
||||
====== |
||||
|
||||
Alt-H2 |
||||
------ |
||||
``` |
||||
|
||||
# H1 |
||||
## H2 |
||||
### H3 |
||||
#### H4 |
||||
##### H5 |
||||
###### H6 |
||||
|
||||
|
||||
Alternatively, for H1 and H2, an underline-ish style: |
||||
|
||||
Alt-H1 |
||||
====== |
||||
|
||||
Alt-H2 |
||||
------ |
||||
|
||||
|
||||
## Emphasis |
||||
|
||||
```markdown |
||||
Emphasis, aka italics, with *asterisks* or _underscores_. |
||||
|
||||
Strong emphasis, aka bold, with **asterisks** or __underscores__. |
||||
|
||||
Combined emphasis with **asterisks and _underscores_**. |
||||
|
||||
Strikethrough uses two tildes. ~~Scratch this.~~ |
||||
``` |
||||
|
||||
Emphasis, aka italics, with *asterisks* or _underscores_. |
||||
|
||||
Strong emphasis, aka bold, with **asterisks** or __underscores__. |
||||
|
||||
Combined emphasis with **asterisks and _underscores_**. |
||||
|
||||
Strikethrough uses two tildes. ~~Scratch this.~~ |
||||
|
||||
|
||||
## Lists |
||||
|
||||
```markdown |
||||
1. First ordered list item |
||||
2. Another item |
||||
* Unordered sub-list. |
||||
1. Actual numbers don't matter, just that it's a number |
||||
1. Ordered sub-list |
||||
4. And another item. |
||||
|
||||
You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). |
||||
|
||||
To have a line break without a paragraph, you will need to use two trailing spaces. |
||||
Note that this line is separate, but within the same paragraph. |
||||
(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) |
||||
|
||||
* Unordered list can use asterisks |
||||
- Or minuses |
||||
+ Or pluses |
||||
- Paragraph In unordered list |
||||
|
||||
For example like this. |
||||
|
||||
Common Paragraph with some text. |
||||
And more text. |
||||
``` |
||||
|
||||
1. First ordered list item |
||||
2. Another item |
||||
* Unordered sub-list. |
||||
1. Actual numbers don't matter, just that it's a number |
||||
1. Ordered sub-list |
||||
4. And another item. |
||||
|
||||
You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). |
||||
|
||||
To have a line break without a paragraph, you will need to use two trailing spaces. |
||||
Note that this line is separate, but within the same paragraph. |
||||
(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) |
||||
|
||||
* Unordered list can use asterisks |
||||
- Or minuses |
||||
+ Or pluses |
||||
- Paragraph In unordered list |
||||
|
||||
For example like this. |
||||
|
||||
Common Paragraph with some text. |
||||
And more text. |
||||
|
||||
## Inline HTML |
||||
|
||||
```markdown |
||||
<p>To reboot your computer, press <kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>del</kbd>.</p> |
||||
``` |
||||
|
||||
<p>To reboot your computer, press <kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>del</kbd>.</p> |
||||
|
||||
|
||||
```markdown |
||||
<dl> |
||||
<dt>Definition list</dt> |
||||
<dd>Is something people use sometimes.</dd> |
||||
|
||||
<dt>Markdown in HTML</dt> |
||||
<dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd> |
||||
</dl> |
||||
``` |
||||
|
||||
|
||||
<dl> |
||||
<dt>Definition list</dt> |
||||
<dd>Is something people use sometimes.</dd> |
||||
|
||||
<dt>Markdown in HTML</dt> |
||||
<dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd> |
||||
</dl> |
||||
|
||||
|
||||
## Links |
||||
|
||||
```markdown |
||||
[I'm an inline-style link](https://www.google.com) |
||||
|
||||
[I'm an inline-style link with title](https://www.google.com "Google's Homepage") |
||||
|
||||
[I'm a reference-style link][Arbitrary case-insensitive reference text] |
||||
|
||||
[I'm a relative reference to a repository file](../blob/master/LICENSE) |
||||
|
||||
[You can use numbers for reference-style link definitions][1] |
||||
|
||||
Or leave it empty and use the [link text itself] |
||||
|
||||
Some text to show that the reference links can follow later. |
||||
|
||||
[arbitrary case-insensitive reference text]: https://hexo.io |
||||
[1]: https://hexo.io/docs/ |
||||
[link text itself]: https://hexo.io/api/ |
||||
``` |
||||
|
||||
[I'm an inline-style link](https://www.google.com) |
||||
|
||||
[I'm an inline-style link with title](https://www.google.com "Google's Homepage") |
||||
|
||||
[I'm a reference-style link][Arbitrary case-insensitive reference text] |
||||
|
||||
[I'm a relative reference to a repository file](../blob/master/LICENSE) |
||||
|
||||
[You can use numbers for reference-style link definitions][1] |
||||
|
||||
Or leave it empty and use the [link text itself] |
||||
|
||||
Some text to show that the reference links can follow later. |
||||
|
||||
[arbitrary case-insensitive reference text]: https://hexo.io |
||||
[1]: https://hexo.io/docs/ |
||||
[link text itself]: https://hexo.io/api/ |
||||
|
||||
## Images |
||||
|
||||
```markdown |
||||
hover to see the title text: |
||||
|
||||
Inline-style: |
||||
|
||||
![alt text](https://hexo.io/icon/favicon-196x196.png "Logo Title Text 1") |
||||
|
||||
Reference-style: |
||||
![alt text][logo] |
||||
|
||||
[logo]: https://hexo.io/icon/favicon-196x196.png "Logo Title Text 2" |
||||
``` |
||||
|
||||
hover to see the title text: |
||||
|
||||
Inline-style: |
||||
|
||||
![alt text](https://hexo.io/icon/favicon-196x196.png "Logo Title Text 1") |
||||
|
||||
Reference-style: |
||||
![alt text][logo] |
||||
|
||||
[logo]: https://hexo.io/icon/favicon-196x196.png "Logo Title Text 2" |
||||
|
||||
## Code and Syntax Highlighting |
||||
|
||||
Inline `code` has `back-ticks around` it. |
||||
|
||||
|
||||
```javascript |
||||
var s = "JavaScript syntax highlighting"; |
||||
alert(s); |
||||
``` |
||||
|
||||
```python |
||||
s = "Python syntax highlighting" |
||||
print s |
||||
``` |
||||
|
||||
``` |
||||
No language indicated, so no syntax highlighting. |
||||
But let's throw in a <b>tag</b>. |
||||
``` |
||||
|
||||
## Tables |
||||
|
||||
```markdown |
||||
| |ASCII |HTML | |
||||
|----------------|-------------------------------|-----------------------------| |
||||
|Single backticks|`'Isn't this fun?'` |'Isn't this fun?' | |
||||
|Quotes |`"Isn't this fun?"` |"Isn't this fun?" | |
||||
|Dashes |`-- is en-dash, --- is em-dash`|-- is en-dash, --- is em-dash| |
||||
``` |
||||
|
||||
| |ASCII |HTML | |
||||
|----------------|-------------------------------|-----------------------------| |
||||
|Single backticks|`'Isn't this fun?'` |'Isn't this fun?' | |
||||
|Quotes |`"Isn't this fun?"` |"Isn't this fun?" | |
||||
|Dashes |`-- is en-dash, --- is em-dash`|-- is en-dash, --- is em-dash| |
||||
|
||||
|
||||
|
||||
|
||||
Colons can be used to align columns. |
||||
|
||||
```markdown |
||||
| Tables | Are | Cool | |
||||
| ------------- |:-------------:| -----:| |
||||
| col 3 is | right-aligned | | |
||||
| col 2 is | centered | | |
||||
| zebra stripes | are neat | |
||||
``` |
||||
|
||||
| Tables | Are | Cool | |
||||
| ------------- |:-------------:| -----:| |
||||
| col 3 is | right-aligned | | |
||||
| col 2 is | centered | | |
||||
| zebra stripes | are neat | | |
||||
|
||||
The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown. |
||||
|
||||
```markdown |
||||
Markdown | Less | Pretty |
||||
--- | --- | --- |
||||
*Still* | `renders` | **nicely** |
||||
1 | 2 | 3 |
||||
``` |
||||
|
||||
Markdown | Less | Pretty |
||||
--- | --- | --- |
||||
*Still* | `renders` | **nicely** |
||||
1 | 2 | 3 |
||||
|
||||
> You can find more information about **LaTeX** mathematical expressions [here](https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference). |
||||
|
||||
|
||||
## Blockquotes |
||||
|
||||
> Blockquotes are very handy in email to emulate reply text. |
||||
> This line is part of the same quote. |
||||
|
||||
Quote break. |
||||
|
||||
> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. |
||||
|
||||
## Horizontal Rule |
||||
|
||||
Three or more... |
||||
|
||||
```markdown |
||||
--- |
||||
|
||||
Hyphens |
||||
|
||||
*** |
||||
|
||||
Asterisks |
||||
|
||||
___ |
||||
|
||||
Underscores |
||||
``` |
||||
|
||||
--- |
||||
|
||||
Hyphens |
||||
|
||||
*** |
||||
|
||||
Asterisks |
||||
|
||||
___ |
||||
|
||||
Underscores |
||||
|
||||
|
||||
## Line Breaks |
||||
|
||||
```markdown |
||||
Here's a line for us to start with. |
||||
|
||||
This line is separated from the one above by two newlines, so it will be a *separate paragraph*. |
||||
|
||||
This line is also a separate paragraph, but... |
||||
This line is only separated by a single newline, so it's a separate line in the *same paragraph*. |
||||
``` |
||||
|
||||
|
||||
Here's a line for us to start with. |
||||
|
||||
This line is separated from the one above by two newlines, so it will be a *separate paragraph*. |
||||
|
||||
This line is also a separate paragraph, but... |
||||
This line is only separated by a single newline, so it's a separate line in the *same paragraph*. |
||||
|
||||
|
||||
----- |
||||
|
||||
```markdown |
||||
This is a regular paragraph. |
||||
|
||||
<table> |
||||
<tr> |
||||
<td>Foo</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
This is another regular paragraph. |
||||
``` |
||||
|
||||
This is a regular paragraph. |
||||
|
||||
<table> |
||||
<tr> |
||||
<td>Foo</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
This is another regular paragraph. |
||||
|
||||
## Youtube videos |
||||
|
||||
```markdown |
||||
<a href="https://www.youtube.com/watch?feature=player_embedded&v=ARted4RniaU |
||||
" target="_blank"><img src="https://img.youtube.com/vi/ARted4RniaU/0.jpg" |
||||
alt="IMAGE ALT TEXT HERE" width="240" height="180" border="10" /></a> |
||||
|
||||
Pure markdown version: |
||||
|
||||
[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/ARted4RniaU/0.jpg)](https://www.youtube.com/watch?v=ARted4RniaU) |
||||
``` |
||||
|
||||
<a href="https://www.youtube.com/watch?feature=player_embedded&v=ARted4RniaU |
||||
" target="_blank"><img src="https://img.youtube.com/vi/ARted4RniaU/0.jpg" |
||||
alt="IMAGE ALT TEXT HERE" width="240" height="180" border="10" /></a> |
||||
|
||||
Pure markdown version: |
||||
|
||||
[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/ARted4RniaU/0.jpg)](https://www.youtube.com/watch?v=ARted4RniaU) |
@ -0,0 +1,5 @@ |
||||
date: 2013-12-25 22:57:49 |
||||
tags: |
||||
--- |
||||
|
||||
This post doesn't have a title. Make sure it's accessible. |
@ -0,0 +1,58 @@ |
||||
title: 寄 |
||||
author: lenfrex |
||||
cover: |
||||
tags: |
||||
- 折腾 |
||||
- 网络 |
||||
- 寄 |
||||
- linux |
||||
- 萌新 |
||||
categories: |
||||
- 折腾 |
||||
date: 2022-03-07 21:22:00 |
||||
--- |
||||
# 没事别乱卸掉linux的python... |
||||
|
||||
# 还有慎用apt autoremove |
||||
|
||||
--- |
||||
|
||||
今天升级一下服务器里边的python版本,由于python2和3有差异,于是把系统里边自带的python2.7给卸了,还把之前装过的py3一起给卸掉了。 |
||||
|
||||
然后觉得不干净就顺手`sudo apt autoremove`了... |
||||
|
||||
网上搜搜autoremove...好吧,我是小白... [apt-get autoremove remove 新手收割者](https://blog.csdn.net/wesigj/article/details/108355649) |
||||
|
||||
apt哗哗哗卸了一大堆软件包...不对劲,一重启....好家伙,ssh超时,甚至服务器都ping不通了... |
||||
|
||||
好在阿里云有vnc可以直接登录tty1,上去之后,ping自己的外网地址和baidu.com,都直接无法访问主机...看起来应该是服务器网络出问题了。 |
||||
|
||||
`ifconfig`一看,果然,本来是连接外网的eth0网卡没了,`ifconfig eth0 up`把网卡挂上,再看,eth0出来了,但是网络还是不通...只有ipv6地址,ipv4地址是空的... |
||||
|
||||
![图不见了哦](https://oss-img.ciduid.top/aether/Snipaste_2022-03-07_17-15-42_2.jpg) |
||||
|
||||
这可怎么办哪 |
||||
|
||||
打开浏览器-->google-->“linux卸载python”-->“apt autoremove” |
||||
|
||||
找了好多博客里边的方法都没管用,都是在有网的前提解决办法...都是直接`apt install -f`,`apt install python`之类的...没用呐 |
||||
|
||||
再翻翻看看,看到一篇十年前的老文章...Σ(っ °Д °;)っ --> [Linux(Ubuntu)卸载Python后-EnjoyKernel-ChinaUnix博客](http://blog.chinaunix.net/uid-26438245-id-3138121.html) |
||||
|
||||
![图不见了哦](https://oss-img.ciduid.top/aether/Snipaste_2022-03-07_21-30-22.png) |
||||
|
||||
好嘛...情况跟他的一样。 |
||||
|
||||
他在 `6,dhclient,成功获取ip地址。(解决问题转折点)` 连接上网络。 |
||||
|
||||
`sudo dhclient`之后,ifconfig,欸,eth0出来了,ip也有了 |
||||
|
||||
![图不见了哦](https://oss-img.ciduid.top/aether/Snipaste_2022-03-07_21-05-00_2.jpg) |
||||
|
||||
好耶! |
||||
|
||||
剩下的就是常规的工作了...把包给补回来吧...但是每次重启都是要手动dhclient...应该是解决了但是没有完全解决... |
||||
|
||||
依赖地狱可不是开玩笑的...觉得还是把数据导出来之后把服务器给重开了吧...还少了点什么谁知道呢... |
||||
|
||||
但是真的不想去动了呀! |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@ |
||||
title: 解决qBittorrent启动时卡死的问题 |
||||
author: lenfrex |
||||
cover: https://oss-img.ciduid.top/blog/covers/cover(3).jpg |
||||
tags: |
||||
- 折腾 |
||||
- 网络 |
||||
categories: |
||||
- 折腾 |
||||
- 日常 |
||||
date: 2022-03-21 20:33:00 |
||||
--- |
||||
自从qBittorrent更新4.4.0版本后,我的qBittorrent启动速度就变得很慢,表现主要为,启动时显示“恢复校验数据”,界面时常卡住无响应(添加种子、查看种子信息等等),cpu占用了20%左右,这根本忍不了好吧。 |
||||
|
||||
以为是自己的机器问题,但是没道理呀,不是我r5 5600x拉吧... |
||||
|
||||
后来又以为是tracker太多的问题,但是我把tracker列表都删掉之后也是一样的问题,没什么用,种子也是只有几个,不叫多吧,况且以前都是秒开的速度,如果是这样的话以前就应该会有这个问题了。 |
||||
|
||||
刚刚上github去找找qBittorrent项目里边的issue看看,搜搜"slow",果然,从2月多就有人报告了这个问题,我在[这个issue](https://github.com/qbittorrent/qBittorrent/issues/16395)里边发现这个回复: |
||||
|
||||
[Slow UI since 4.4.0 · Issue #16395 · qbittorrent/qBittorrent · GitHub](https://github.com/qbittorrent/qBittorrent/issues/16395#issuecomment-1031860720) |
||||
|
||||
![](https://oss-img.ciduid.top/aether/Snipaste_2022-03-21_21-26-03.png) |
||||
|
||||
这位老哥把网卡改成了他的wifi网卡,而不是默认的"任意连接",启动速度就恢复了正常 |
||||
|
||||
我按着他的做法,去qBittorrent上面的高级设置里边的“网络接口”修改一下。 |
||||
|
||||
可以看到,在我这里有几个虚拟机的虚拟网卡还有其他的东西,把默认的“任意网络接口”选项改成了我这里的"WUST-TELECOM"拨号连接,为什么是拨号连接...这得问学校...一般如果能直接联网的话就选择“以太网”,看你上网联网的是哪个连接去选择。 |
||||
|
||||
![](https://oss-img.ciduid.top/aether/Snipaste_2022-03-21_21-34-58.png) |
||||
|
||||
经过这番操作之后终于好了,直接恢复原来的秒开速度,界面也不会经常卡住了。 |
||||
|
||||
看issue貌似挺多人也有这个问题呀 |
@ -0,0 +1,537 @@ |
||||
title: 某校园网认证api分析 |
||||
author: lensfrex |
||||
cover: 'https://oss-img.ciduid.top/blog/covers/61391031_p0_compressed.png' |
||||
tags: |
||||
- 网络 |
||||
categories: |
||||
- 网络 |
||||
date: 2022-07-06 13:22:00 |
||||
--- |
||||
|
||||
> 封面:https://www.pixiv.net/artworks/61391031 |
||||
|
||||
--- |
||||
|
||||
*最近突然来了兴趣,想搞一搞校园网认证页的认证api,方便以后自动登录,而不是每次联网都要手动登陆一遍* |
||||
|
||||
*虽然我这里可以直接用拨号登录,而且我已经用上了路由器,直接全程连接,更加方便,但是还是想搞一搞这个登录认证* |
||||
|
||||
*这里只是在研究认证页的时候的随手记,如果直接看的话可能会一脸懵逼,等有空了我再补充详细的过程吧(咕)* |
||||
|
||||
--- |
||||
> ### 后续来啦:[这里](https://www.ciduid.top/2022/1113/srun-login-analyze/) |
||||
--- |
||||
|
||||
学校校园网登录认证的过程大致分为两步 |
||||
|
||||
## 1. 获取challenge码 |
||||
|
||||
这部分简单,直接发学号用户名(学号)就行了 |
||||
|
||||
Base URL:`http://59.68.177.183/cgi-bin/get_challenge` |
||||
|
||||
方法:GET |
||||
|
||||
请求参数: |
||||
|
||||
``` apache |
||||
callback: jQuery112409362602503309623_1657072809873 |
||||
username: 【学号】 |
||||
ip: 10.15.2.109 |
||||
_: 1657072809875 |
||||
|
||||
``` |
||||
callback: 回调函数,后半部分为时间戳,不设置callback的话只有ok |
||||
|
||||
username: 顾名思义 |
||||
|
||||
ip: 顾名思义 |
||||
|
||||
_: 时间戳 |
||||
|
||||
注意:这里的时间戳是GMT时间,而不是咱们的东八区时间 |
||||
|
||||
服务器返回数据,有用的是`challenge` |
||||
|
||||
``` apache |
||||
challenge: "4d8e7d882e52c17537e1203191a8308fe294520bfb8e15c1f860bd1b03d983cc" |
||||
client_ip: "10.15.2.109" |
||||
ecode: 0 |
||||
error: "ok" |
||||
error_msg: "" |
||||
expire: "60" |
||||
online_ip: "10.15.2.109" |
||||
res: "ok" |
||||
srun_ver: "SRunCGIAuthIntfSvr V1.18 B20211105" |
||||
st: 1657074027 |
||||
``` |
||||
--- |
||||
|
||||
## 2. 根据challenge码进行验证 |
||||
|
||||
这部分就有点复杂了,但还好,浏览器控制台跳几跳就看得出来是怎样的了 |
||||
|
||||
Base URL:`http://59.68.177.183/cgi-bin/srun_portal` |
||||
|
||||
方法:GET |
||||
|
||||
请求参数 |
||||
|
||||
``` apache |
||||
callback: jQuery112409362602503309623_1657072809873 |
||||
action: login |
||||
username: 【学号】 |
||||
password: {MD5}a25f5a06d8484669fb3ca3df0362efc8 |
||||
os: Windows 10 |
||||
name: Windows |
||||
double_stack: 0 |
||||
chksum: be73bd397721c3f39392e3fbfcc8124be9d0516a |
||||
info: {SRBX1}F1DdbTEkXB8FyHKlvt+GnvkjogeyV6HZst2G+VWWo684jx/32muHiDv3fKLh9AwIoaUCsu2rRaLlOB2Te5A4lYSFqCPLGJizZ5KsBUX5t6tNcN6coDACovbe4Pv0fF0pulYtqpy+i6D= |
||||
ac_id: 7 |
||||
ip: 10.15.2.109 |
||||
n: 200 |
||||
type: 1 |
||||
_: 1657072809876 |
||||
``` |
||||
|
||||
`password`字段的MD5由明文密码和上面得到的`challenge`码拼接之后得到的MD5 |
||||
|
||||
``` apache |
||||
ServerFlag: 0 |
||||
ServicesIntfServerIP: "172.26.23.173" |
||||
ServicesIntfServerPort: "8001" |
||||
access_token: "4d8e7d882e52c17537e1203191a8308fe294520bfb8e15c1f860bd1b03d983cc" |
||||
checkout_date: 0 |
||||
client_ip: "10.15.2.109" |
||||
ecode: 0 |
||||
error: "ok" |
||||
error_msg: "" |
||||
online_ip: "10.15.2.109" |
||||
ploy_msg: "E0000: Login is successful." |
||||
real_name: "" |
||||
remain_flux: 0 |
||||
remain_times: 0 |
||||
res: "ok" |
||||
srun_ver: "SRunCGIAuthIntfSvr V1.18 B20211105" |
||||
suc_msg: "login_ok" |
||||
sysver: "1.01.20211105" |
||||
username: "【学号】" |
||||
wallet_balance: 0 |
||||
``` |
||||
|
||||
在这里把登录认证发送部分的js代码挂上来(没有完全混淆代码而且还有注释,太棒啦): |
||||
|
||||
这里的`token`即是之前获取到的`challenge` |
||||
|
||||
请求部分的代码在此: |
||||
<details> |
||||
<summary>查看完整代码</summary> |
||||
|
||||
```javascript |
||||
_loginAccount.set(_assertThisInitialized(_this), { |
||||
writable: true, |
||||
value: function value(obj) { |
||||
// 加密常量 |
||||
var type = 1; |
||||
var n = 200; |
||||
var enc = 'srun_bx1'; |
||||
// 用户信息 |
||||
|
||||
var username = _this.userInfo.username + _this.userInfo.domain; |
||||
var password = _this.userInfo.password; |
||||
var ac_id = _this.portalInfo.acid; |
||||
// 正在等待中的请求 |
||||
|
||||
var pendingReqNum = 0; |
||||
// 请求成功的消息 |
||||
|
||||
var successMsg = ''; |
||||
// 发起认证方法 |
||||
|
||||
var sendAuth = function sendAuth() { |
||||
var host = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; |
||||
// 双栈认证时 IP 参数为空 |
||||
var ip = _this.portalInfo.doub && host ? '' : _this.userInfo.ip; |
||||
// 获取 Token |
||||
|
||||
_classPrivateFieldGet(_assertThisInitialized(_this), _getToken) |
||||
.call(_assertThisInitialized(_this), host, ip, function(token) { |
||||
// 用户密码 MD5 加密 |
||||
var hmd5 = md5(password, token); |
||||
// 用户信息加密 |
||||
|
||||
var i = _classPrivateFieldGet(_assertThisInitialized(_this), _encodeUserInfo).call(_assertThisInitialized(_this), { |
||||
username: username, |
||||
password: password, |
||||
ip: ip, |
||||
acid: ac_id, |
||||
enc_ver: enc |
||||
}, token); |
||||
|
||||
var str = token + username; |
||||
str += token + hmd5; |
||||
str += token + ac_id; |
||||
str += token + ip; |
||||
str += token + n; |
||||
str += token + type; |
||||
str += token + i; |
||||
// 防止 IPv6 请求网络不通进行 try catch |
||||
|
||||
try { |
||||
pendingReqNum += 1; |
||||
// 发起认证请求 |
||||
|
||||
_this.ajax.jsonp({ |
||||
host: host, |
||||
url: _classPrivateFieldGet(_assertThisInitialized(_this), _api) |
||||
.auth, |
||||
params: { |
||||
action: 'login', |
||||
username: username, |
||||
password: _this.userInfo.otp ? '{OTP}' + password : '{MD5}' + hmd5, |
||||
os: _this.portalInfo.userDevice.device, |
||||
name: _this.portalInfo.userDevice.platform, |
||||
// 未开启双栈认证,参数为 0 |
||||
// 开启双栈认证,向 Portal 当前页面 IP 认证时,参数为 1 |
||||
// 开启双栈认证,向 Portal 另外一种 IP 认证时,参数为 0 |
||||
double_stack: _this.portalInfo.doub && !host ? 1 : 0, |
||||
chksum: sha1(str), |
||||
info: i, |
||||
ac_id: ac_id, |
||||
ip: ip, |
||||
n: n, |
||||
type: type |
||||
}, |
||||
success: function success(res) { |
||||
pendingReqNum -= 1; |
||||
// 认证成功,用户上线 |
||||
|
||||
_this.online = true; |
||||
// 更改登录状态为结束 |
||||
|
||||
_this.running.login = false; |
||||
// IP 已经在线了 - 给出提示 |
||||
|
||||
if (res.suc_msg === 'ip_already_online_error' && obj.error) |
||||
return _this.confirm({ |
||||
message: _this.translate('ip_already_online_error'), |
||||
confirm: function confirm() { |
||||
if (obj.error) |
||||
obj.error(); |
||||
} |
||||
}); |
||||
// 翻译后的认证成功信息 |
||||
|
||||
successMsg = _this.translate(res); |
||||
}, |
||||
error: function error(res) { |
||||
pendingReqNum -= 1; |
||||
// 更改登录状态为结束 |
||||
|
||||
_this.running.login = false; |
||||
// 若 ecode 为 E2620 且开启了在线设备管理功能 |
||||
// E2620: 超出允许的在线数目 |
||||
|
||||
if (res.ecode === 'E2620' && CREATER.useOnlineDeviceMgr) |
||||
return _this.confirm({ |
||||
message: _this.translate('E2620Tips'), |
||||
confirm: function confirm() { |
||||
_this.dialog.open('onlineDeviceMgr', function() { |
||||
_this.getOnlineDevice(); |
||||
}); |
||||
} |
||||
}); |
||||
// IP 已经在线了 - 重新认证 |
||||
|
||||
if (res.error_msg === 'ip_already_online_error') |
||||
return _this.reAuth(obj); |
||||
// 需要查询日志的情况 |
||||
|
||||
if (res.error_msg === 'not_online_error') |
||||
return _this.showLog(); |
||||
if (res.error_msg === 'no_response_data_error') |
||||
return _this.showLog(); |
||||
if (res.error_msg === 'RD000') |
||||
return _this.showLog(); |
||||
// 若提示修改密码 |
||||
|
||||
if (res.error_msg === 'user_must_modify_password') |
||||
return _this.confirm({ |
||||
message: _this.translate(res), |
||||
confirmText: _this.translate('ToChangePassword'), |
||||
confirm: function confirm() { |
||||
return $('#forget') |
||||
.click(); |
||||
}, |
||||
cancel: function cancel() {} |
||||
}); |
||||
// 错误提示 |
||||
|
||||
_this.confirm({ |
||||
message: _this.translate(res), |
||||
confirm: function confirm() { |
||||
if (obj.error) |
||||
obj.error(res); |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} catch (err) { |
||||
// 因为 IPv6 网络问题导致的认证失败 |
||||
pendingReqNum -= 1; |
||||
} |
||||
}); |
||||
}; |
||||
``` |
||||
</details> |
||||
|
||||
上面的`portalInfo`是从`config`来的,而`config`定义在登录认证首页下面: |
||||
|
||||
``` javascript |
||||
var CONFIG = { |
||||
page : 'account', |
||||
ip : "10.15.2.109", |
||||
nas : "", |
||||
mac : "", |
||||
url : "", |
||||
lang : "zh-CN" || 'zh-CN', |
||||
isIPV6 : false , |
||||
portal : {"AuthIP":"","AuthIP6":"","ServiceIP":"https://59.68.177.181:8800","DoubleStackPC":false,"DoubleStackMobile":false,"AuthMode":false,"CloseLogout":false,"MacAuth":false,"RedirectUrl":true,"OtherPCStack":"IPV4","OtherMobileStack":"IPV4","MsgApi":"new","PublicSuccessPages":true,"TrafficCarry":1000,"UserAgreeSwitch":false,"DialSwitch":false}, |
||||
notice : "list", |
||||
priceList : {"Prices":"5,10,20,50,100","Default":"0.01","BalanceWarning":"5"} |
||||
}; |
||||
``` |
||||
|
||||
info字段的数据是在下面这里生成的,关键是encode函数,具体是什么算法看不出来 |
||||
|
||||
encode函数生成一段神奇的编码数据之后再用一种动过手脚的base64编码一下就得到了上面的i变量 |
||||
|
||||
把这段代码算法转成其他其他语言就好办很多了 |
||||
|
||||
<details> |
||||
<summary>展开查看代码(js)</summary> |
||||
|
||||
``` javascript |
||||
_encodeUserInfo.set(_assertThisInitialized(_this), { |
||||
writable: true, |
||||
value: function value(info, token) { |
||||
// 克隆自 $.base64,防止污染 |
||||
var base64 = _this.clone($.base64); |
||||
// base64 设置 Alpha |
||||
|
||||
base64.setAlpha('LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA'); |
||||
// 用户信息转 JSON |
||||
|
||||
info = JSON.stringify(info); |
||||
|
||||
function encode(str, key) { |
||||
if (str === '') |
||||
return ''; |
||||
var v = s(str, true); |
||||
var k = s(key, false); |
||||
if (k.length < 4) |
||||
k.length = 4; |
||||
var n = v.length - 1, z = v[n], y = v[0], c = 0x86014019 | 0x183639A0, m, e, p, q = Math.floor(6 + 52 / (n + 1)), d = 0; |
||||
|
||||
while (0 < q--) { |
||||
d = d + c & (0x8CE0D9BF | 0x731F2640); |
||||
e = d >>> 2 & 3; |
||||
|
||||
for (p = 0; p < n; p++) { |
||||
y = v[p + 1]; |
||||
m = z >>> 5 ^ y << 2; |
||||
m += y >>> 3 ^ z << 4 ^ (d ^ y); |
||||
m += k[p & 3 ^ e] ^ z; |
||||
z = v[p] = v[p] + m & (0xEFB8D130 | 0x10472ECF); |
||||
} |
||||
|
||||
y = v[0]; |
||||
m = z >>> 5 ^ y << 2; |
||||
m += y >>> 3 ^ z << 4 ^ (d ^ y); |
||||
m += k[p & 3 ^ e] ^ z; |
||||
z = v[n] = v[n] + m & (0xBB390742 | 0x44C6F8BD); |
||||
} |
||||
|
||||
return l(v, false); |
||||
} |
||||
|
||||
function s(a, b) { |
||||
var c = a.length; |
||||
var v = []; |
||||
|
||||
for (var i = 0; i < c; i += 4) { |
||||
v[i >> 2] = a.charCodeAt(i) | a.charCodeAt(i + 1) << 8 | a.charCodeAt(i + 2) << 16 | a.charCodeAt(i + 3) << 24; |
||||
} |
||||
|
||||
if (b) |
||||
v[v.length] = c; |
||||
return v; |
||||
} |
||||
|
||||
function l(a, b) { |
||||
var d = a.length; |
||||
var c = d - 1 << 2; |
||||
|
||||
if (b) { |
||||
var m = a[d - 1]; |
||||
if (m < c - 3 || m > c) |
||||
return null; |
||||
c = m; |
||||
} |
||||
|
||||
for (var i = 0; i < d; i++) { |
||||
a[i] = String.fromCharCode(a[i] & 0xff, a[i] >>> 8 & 0xff, a[i] >>> 16 & 0xff, a[i] >>> 24 & 0xff); |
||||
} |
||||
|
||||
return b ? a.join('').substring(0, c) : a.join(''); |
||||
} |
||||
|
||||
return '{SRBX1}' + base64.encode(encode(info, token)); |
||||
} |
||||
}); |
||||
``` |
||||
</details> |
||||
|
||||
用go简单实现了一下这段加密算法: |
||||
|
||||
<details> |
||||
<summary>展开查看代码(go)</summary> |
||||
|
||||
```go |
||||
import ( |
||||
"encoding/base64" |
||||
"math" |
||||
) |
||||
|
||||
// 这里的base64和正常的base64编码是不一样的,这里的base64并不按照普通的ABCD字母顺序来对应相应字节,而是按照以下字母表对应字节 |
||||
const magicBase64Alpha = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA" |
||||
|
||||
var magicBase64 = base64.NewEncoding(magicBase64Alpha) |
||||
|
||||
func Encode(str string, key string) string { |
||||
encodeResult := encode(str, key) |
||||
base64Result := magicBase64.EncodeToString(int32ToAsciiBytes(encodeResult)) |
||||
|
||||
return "{SRBX1}" + base64Result |
||||
} |
||||
|
||||
// encode 直接照着原网页的js代码改的,只是能跑 |
||||
// 因为各种类型转换从理论上来说性能可能会差了点,但是在这里的使用情景几乎是感觉不到的 |
||||
// 难度其实主要还是在于动态类型语言和静态类型语言在运算时数据溢出的问题不好处理 |
||||
func encode(str string, key string) []int32 { |
||||
v := magicEncode(str, true) |
||||
k := magicEncode(key, false) |
||||
|
||||
if len(k) < 4 { |
||||
for i := 0; i < (4 - len(k)); i++ { |
||||
k = append(k, 0) |
||||
} |
||||
} |
||||
|
||||
n := int32(len(v) - 1) |
||||
z := v[n] |
||||
y := v[0] |
||||
c := int32(-1640531527) |
||||
q := int(math.Floor(float64(6+52/(n+1)))) - 1 |
||||
d := int32(0) |
||||
var e, p int32 |
||||
|
||||
// 这里的m用int64(long)是因为后面+=的时候int32会溢出 |
||||
// 其他变量不用int64是因为直接用int64会导致位运算错误,和js的运算结果不一致 |
||||
var m int64 |
||||
|
||||
for ; q >= 0; q-- { |
||||
d = d + c&(-1) |
||||
e = uRightShift(d, 2) & 3 |
||||
|
||||
for p = 0; p < n; p++ { |
||||
y = v[p+1] |
||||
m = int64(uRightShift(z, 5) ^ y<<2) |
||||
m += int64(uRightShift(y, 3) ^ z<<4 ^ (d ^ y)) |
||||
m += int64(k[p&3^e] ^ z) |
||||
v[p] = v[p] + int32(m&(-1)) |
||||
z = v[p] |
||||
} |
||||
|
||||
y = v[0] |
||||
m = int64(uRightShift(z, 5) ^ y<<2) |
||||
m += int64(uRightShift(y, 3) ^ z<<4 ^ (d ^ y)) |
||||
m += int64(k[p&3^e] ^ z) |
||||
v[n] = v[n] + int32(m&(-1)) |
||||
z = v[n] |
||||
} |
||||
|
||||
return magicDecode(v, false) |
||||
} |
||||
|
||||
// 神秘的“初步”加密代码,把字符串一四个字符为一组转成神秘的int32数组 |
||||
func magicEncode(source string, sizeOnLast bool) (result []int32) { |
||||
data := []int32(source) |
||||
dataLen := len(data) |
||||
|
||||
resultLen := dataLen / 4 |
||||
if sizeOnLast { |
||||
result = make([]int32, resultLen+1) |
||||
result[resultLen] = int32(dataLen) |
||||
} else { |
||||
result = make([]int32, resultLen) |
||||
} |
||||
|
||||
for i := 0; i < dataLen; i += 4 { |
||||
result[i>>2] = get(data, i, dataLen) | get(data, i+1, dataLen)<<8 | get(data, i+2, dataLen)<<16 | get(data, i+3, dataLen)<<24 |
||||
} |
||||
|
||||
return result |
||||
} |
||||
|
||||
//和上面的是反过来的 |
||||
func magicDecode(data []int32, sizeOnLast bool) (result []int32) { |
||||
dataLength := len(data) |
||||
|
||||
c := dataLength - 1<<2 |
||||
|
||||
if sizeOnLast { |
||||
m := int(data[dataLength-1]) |
||||
if m < c-3 || m > c { |
||||
return nil |
||||
} |
||||
c = m |
||||
} |
||||
|
||||
for i := 0; i < dataLength; i++ { |
||||
result = append(result, data[i]&0xff, uRightShift(data[i], 8)&0xff, uRightShift(data[i], 16)&0xff, uRightShift(data[i], 24)&0xff) |
||||
} |
||||
|
||||
if sizeOnLast { |
||||
return append(result, int32(c)) |
||||
} else { |
||||
return result |
||||
} |
||||
} |
||||
|
||||
// 在go中实现无符号右移(>>>) |
||||
func uRightShift(number int32, shift int) int32 { |
||||
return int32(uint32(number) >> shift) |
||||
} |
||||
|
||||
func get(data []int32, index int, length int) int32 { |
||||
if index >= length { |
||||
return 0 |
||||
} else { |
||||
return data[index] |
||||
} |
||||
} |
||||
|
||||
func int32ToAsciiBytes(data []int32) []byte { |
||||
result := make([]byte, len(data)) |
||||
for i, number := range data { |
||||
result[i] = byte(number) |
||||
} |
||||
|
||||
return result |
||||
} |
||||
|
||||
``` |
||||
</details> |
||||
|
||||
更详细的加密算法分析过程请看[这里](https://www.ciduid.top/2022/1113/srun-login-analyze/) |
@ -0,0 +1,92 @@ |
||||
title: 关于Spring里类的私有字段的一些坑 |
||||
author: lensfrex |
||||
cover: 'https://oss-img.ciduid.top/blog/covers/cover(10).jpg' |
||||
tags: |
||||
- Java |
||||
- 笔记 |
||||
categories: [] |
||||
date: 2022-07-11 15:42:00 |
||||
--- |
||||
|
||||
封面:https://www.pixiv.net/artworks/98234187 |
||||
|
||||
--- |
||||
|
||||
话先说在前头: |
||||
|
||||
> 记得写上getter/setter方法 |
||||
|
||||
--- |
||||
|
||||
我们写统一响应类一般会这么写: |
||||
|
||||
``` java |
||||
package net.lensfrex.dscape.domain.response; |
||||
|
||||
public class Response<T> { |
||||
private final int code; |
||||
private final String message; |
||||
private final T data; |
||||
|
||||
public Response(int code, String message, T data) { |
||||
this.code = code; |
||||
this.message = message; |
||||
this.data = data; |
||||
} |
||||
|
||||
public static<T> Response<T> success(T data) { |
||||
return new Response<>(20000, "success", data); |
||||
} |
||||
|
||||
public static Response<Object> error(int code, String message) { |
||||
return new Response<>(code, message, null); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
在controller里边如果是这样写: |
||||
|
||||
```java |
||||
@RestController |
||||
@RequestMapping("/user") |
||||
public class User { |
||||
|
||||
@PostMapping(value = "/login" , produces = "application/json") |
||||
public Response<LoginResponseData> result(@RequestBody String body) { |
||||
LoginResponseData loginResponseData = new LoginResponseData(); |
||||
loginResponseData.setRole(0); |
||||
loginResponseData.setUid("uuid"); |
||||
loginResponseData.setAccessToken("token"); |
||||
loginResponseData.setExpired(120202002); |
||||
loginResponseData.setRefreshToken("refreshToken"); |
||||
|
||||
|
||||
return Response.success(loginResponseData); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
当我们发起请求的时候,会出现这样的问题: |
||||
|
||||
``` json |
||||
{ |
||||
"timestamp": "2022-07-11T07:49:09.656+00:00", |
||||
"status": 500, |
||||
"error": "Internal Server Error", |
||||
"path": "/user/login" |
||||
} |
||||
``` |
||||
|
||||
程序会打出这样的日志: |
||||
|
||||
`2022-07-11 15:49:09.652 WARN 14728 --- [tp1298557761-40] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class net.lensfrex.dscape.domain.response.Response] with preset Content-Type 'null']` |
||||
|
||||
也就是说没法把这个类给转换成json |
||||
|
||||
这是因为在Spring MVC里面将实体Bean转换成json时,是通过对应字段的getter来获取到其值的。我们这里并没有为其提供相应的getter方法,所以没法获取到字段的值,因为所有字段都是`private`,所以得到的也都是`null`,也就没法转成json,产生错误。 |
||||
|
||||
因此在编写统一返回类的时候应当要注意带上getter方法。 |
||||
|
||||
> 番外: |
||||
> |
||||
> 如果想让值为`null`的字段不参与序列化,则在这个类标注上`@JsonInclude(JsonInclude.Include.NON_NULL)`,这样,在json输出的时候,这个null字段就会消失,在json中不会有这个key |
@ -0,0 +1,742 @@ |
||||
title: wust武科大校园网深澜srun认证api分析(后续) |
||||
author: lensfrex |
||||
cover: https://oss-img.ciduid.top/blog/covers/90778009_p0_compress.png |
||||
tags: |
||||
- 网络 |
||||
categories: |
||||
- 网络 |
||||
date: 2022-11-13 20:22:00 |
||||
--- |
||||
|
||||
--- |
||||
|
||||
> 上回书说到,彼时的璃月.... |
||||
|
||||
书接上回:[某校园网认证api分析](/2022/0706/school-network-auth/) |
||||
|
||||
上一次对校园网web认证做了个简单的请求分析,但是一些关键的字段是用了某种神奇的算法进行了加密发送,这里就对这个算法进行简单的分析,并且用go语言重新实现一遍 |
||||
|
||||
(说起来好像从7月初就一直鸽到了现在了吧...嘘🤫) |
||||
|
||||
--- |
||||
|
||||
## 抓包分析 |
||||
|
||||
干这种事情首先要做的就是抓包,最简单的就是直接浏览器F12,完整地过一次登录请求 |
||||
|
||||
### 1. 请求challenge码 |
||||
|
||||
最先发出来的是一个GET请求: |
||||
|
||||
`http://59.68.177.183/cgi-bin/get_challenge?callback=jQuery1124005588867363182781_1668219915986&username=202100000000&ip=10.16.1.9&_=1668219915990` |
||||
|
||||
请求参数: |
||||
``` |
||||
callback: jQuery1124005588867363182781_1668219915986 |
||||
username: 202100000000 |
||||
ip: 10.16.1.9 |
||||
_: 1668219915990 |
||||
``` |
||||
|
||||
这里的callback一定要设置,否则返回的是杂乱的数据(没有json结构),只有一个ok,是没有咱们想要的challenge码的 |
||||
|
||||
其他字段顾名思义,ip就是当前连接之后获取到的ip,下划线字段是当前时间戳(毫秒),注意这个时间是GMT时间,而不是咱们的东八区时间 |
||||
|
||||
获取到的响应如下: |
||||
```json |
||||
jQuery1124005588867363182781_1668219915986({ |
||||
"challenge": "3c6d08d667d0ee0ccad77c55b19d3e4ab2552f7163ec40a9389095a18f86c398", |
||||
"client_ip": "10.16.1.9", |
||||
"ecode": 0, |
||||
"error": "ok", |
||||
"error_msg": "", |
||||
"expire": "52", |
||||
"online_ip": "10.16.1.9", |
||||
"res": "ok", |
||||
"srun_ver": "SRunCGIAuthIntfSvr V1.18 B20211105", |
||||
"st": 1668219964 |
||||
}) |
||||
``` |
||||
这里我们只需要关心challenge字段的值就好了,后面有用。 |
||||
|
||||
### 2. 请求认证 |
||||
|
||||
继续分析,接下来的也是一个GET请求: |
||||
|
||||
`http://59.68.177.183/cgi-bin/srun_portal?callback=jQuery1124005588867363182781_1668219915986&action=login&username=202100000000&password=%7BMD5%7Dr096b1282e1a50ce8f9d15aa3a29acf8&os=Linux&name=Linux&double_stack=0&chksum=dfa1459124878b981873bd6d853c88b1eb716e6d&info=%7BSRBX1%7D76z3vHCupat5bbo3et2MbNplTCn0FXWKd%2FhazIzb26HpqJsIoolCvtcLPk5mnstlz0J%2BebaMjGfckCzVHlaUqqGaAJ7XM%2FEQ83p0D2TPbjDG7f%2FiFEiadcQkHJpiwOBr800LDP7yrA4%3D&ac_id=7&ip=10.16.1.9&n=200&type=1&_=1668219915991` |
||||
|
||||
请求参数(部分字段经过修改,请以实际为准): |
||||
|
||||
``` |
||||
callback: jQuery1124005588867363182781_1668219915986 |
||||
action: login |
||||
username: 202100000000 |
||||
password: {MD5}r096b1282e1a50ce8f9d15aa3a29acf8 |
||||
os: Linux |
||||
name: Linux |
||||
double_stack: 0 |
||||
chksum: dfa1459124878b981873bd6d853c88b1eb716e6d |
||||
info: {SRBX1}76z3vHCupat5bbo3et2MbNplTCn0FXWKd/hazIzs26HpqJsIoolCvtcLPd5mnstlz0J+ebaMjGfckCzVHlaUqqGaAJ7XM/EQ83p0D2TPbsDG7f/iFEiadcQkHJpiwOBr8002DP7yrA4= |
||||
ac_id: 7 |
||||
ip: 10.16.1.9 |
||||
n: 200 |
||||
type: 1 |
||||
_: 1668219915991 |
||||
``` |
||||
|
||||
翻看js源码,不难发现大部分字段其实是写死了的(在下一节会详细讲),动态的字段也比较好算出来,除了info字段算法比较特殊外,其他的都是普通的算法,翻翻js源码就知道了 |
||||
|
||||
这个请求完成以后,不出意外的话,就能上网了 |
||||
|
||||
这个请求的响应如下: |
||||
```json |
||||
jQuery1124005588867363182781_1668219915986({ |
||||
"ServerFlag": 0, |
||||
"ServicesIntfServerIP": "172.26.23.173", |
||||
"ServicesIntfServerPort": "8001", |
||||
"access_token": "3c6d08d667d0ee0ccad77c55b19d3e4ab2552f7163ec40a9389095a18f86c398", |
||||
"checkout_date": 0, |
||||
"client_ip": "10.16.1.9", |
||||
"ecode": 0, |
||||
"error": "ok", |
||||
"error_msg": "", |
||||
"online_ip": "10.16.1.9", |
||||
"ploy_msg": "E0000: Login is successful.", |
||||
"real_name": "", |
||||
"remain_flux": 0, |
||||
"remain_times": 0, |
||||
"res": "ok", |
||||
"srun_ver": "SRunCGIAuthIntfSvr V1.18 B20211105", |
||||
"suc_msg": "ip_already_online_error", |
||||
"sysver": "1.01.20211105", |
||||
"username": "202100000000", |
||||
"wallet_balance": 0 |
||||
}) |
||||
``` |
||||
这下认证的请求部分就结束了 |
||||
|
||||
## 刨 |
||||
|
||||
看完了请求是怎么请求的,现在就来看看这些请求的数据是怎么个算出来的吧 |
||||
|
||||
--- |
||||
|
||||
这个认证页面主要的业务逻辑主要是放在了[`Portal.js`](http://59.68.177.183/static/themes/pro/js/Portal.js?v=2.00.20211105)这个js脚本里 |
||||
|
||||
这个js脚本简直是好得不得了,只有简单的混淆(或者说...其实根本就没混淆),而且还有详细的注释,真的是太良心了 |
||||
|
||||
简单找找,能够发现负责请求的函数就是这段(大约在995行附近): |
||||
|
||||
<details> |
||||
<summary>小心,有巨量代码</summary> |
||||
|
||||
```javascript |
||||
value: function value(obj) { |
||||
// 加密常量 |
||||
var type = 1; |
||||
var n = 200; |
||||
var enc = 'srun_bx1'; // 用户信息 |
||||
|
||||
var username = _this.userInfo.username + _this.userInfo.domain; |
||||
var password = _this.userInfo.password; |
||||
var ac_id = _this.portalInfo.acid; // 正在等待中的请求 |
||||
|
||||
var pendingReqNum = 0; // 请求成功的消息 |
||||
|
||||
var successMsg = ''; // 发起认证方法 |
||||
|
||||
var sendAuth = function sendAuth() { |
||||
var host = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; |
||||
// 双栈认证时 IP 参数为空 |
||||
var ip = _this.portalInfo.doub && host ? '' : _this.userInfo.ip; // 获取 Token |
||||
|
||||
_classPrivateFieldGet(_assertThisInitialized(_this), _getToken).call(_assertThisInitialized(_this), host, ip, function (token) { |
||||
// 用户密码 MD5 加密 |
||||
var hmd5 = md5(password, token); // 用户信息加密 |
||||
|
||||
var i = _classPrivateFieldGet(_assertThisInitialized(_this), _encodeUserInfo).call(_assertThisInitialized(_this), { |
||||
username: username, |
||||
password: password, |
||||
ip: ip, |
||||
acid: ac_id, |
||||
enc_ver: enc |
||||
}, token); |
||||
|
||||
var str = token + username; |
||||
str += token + hmd5; |
||||
str += token + ac_id; |
||||
str += token + ip; |
||||
str += token + n; |
||||
str += token + type; |
||||
str += token + i; // 防止 IPv6 请求网络不通进行 try catch |
||||
|
||||
try { |
||||
pendingReqNum += 1; // 发起认证请求 |
||||
|
||||
_this.ajax.jsonp({ |
||||
host: host, |
||||
url: _classPrivateFieldGet(_assertThisInitialized(_this), _api).auth, |
||||
params: { |
||||
action: 'login', |
||||
username: username, |
||||
password: _this.userInfo.otp ? '{OTP}' + password : '{MD5}' + hmd5, |
||||
os: _this.portalInfo.userDevice.device, |
||||
name: _this.portalInfo.userDevice.platform, |
||||
// 未开启双栈认证,参数为 0 |
||||
// 开启双栈认证,向 Portal 当前页面 IP 认证时,参数为 1 |
||||
// 开启双栈认证,向 Portal 另外一种 IP 认证时,参数为 0 |
||||
double_stack: _this.portalInfo.doub && !host ? 1 : 0, |
||||
chksum: sha1(str), |
||||
info: i, |
||||
ac_id: ac_id, |
||||
ip: ip, |
||||
n: n, |
||||
type: type |
||||
}, |
||||
success: function success(res) { |
||||
pendingReqNum -= 1; // 认证成功,用户上线 |
||||
|
||||
_this.online = true; // 更改登录状态为结束 |
||||
|
||||
_this.running.login = false; // IP 已经在线了 - 给出提示 |
||||
|
||||
if (res.suc_msg === 'ip_already_online_error' && obj.error) return _this.confirm({ |
||||
message: _this.translate('ip_already_online_error'), |
||||
confirm: function confirm() { |
||||
if (obj.error) obj.error(); |
||||
} |
||||
}); // 翻译后的认证成功信息 |
||||
|
||||
successMsg = _this.translate(res); |
||||
}, |
||||
error: function error(res) { |
||||
pendingReqNum -= 1; // 更改登录状态为结束 |
||||
|
||||
_this.running.login = false; // 若 ecode 为 E2620 且开启了在线设备管理功能 |
||||
// E2620: 超出允许的在线数目 |
||||
|
||||
if (res.ecode === 'E2620' && CREATER.useOnlineDeviceMgr) return _this.confirm({ |
||||
message: _this.translate('E2620Tips'), |
||||
confirm: function confirm() { |
||||
_this.dialog.open('onlineDeviceMgr', function () { |
||||
_this.getOnlineDevice(); |
||||
}); |
||||
} |
||||
}); // IP 已经在线了 - 重新认证 |
||||
|
||||
if (res.error_msg === 'ip_already_online_error') return _this.reAuth(obj); // 需要查询日志的情况 |
||||
|
||||
if (res.error_msg === 'not_online_error') return _this.showLog(); |
||||
if (res.error_msg === 'no_response_data_error') return _this.showLog(); |
||||
if (res.error_msg === 'RD000') return _this.showLog(); // 若提示修改密码 |
||||
|
||||
if (res.error_msg === 'user_must_modify_password') return _this.confirm({ |
||||
message: _this.translate(res), |
||||
confirmText: _this.translate('ToChangePassword'), |
||||
confirm: function confirm() { |
||||
return $('#forget').click(); |
||||
}, |
||||
cancel: function cancel() {} |
||||
}); // 错误提示 |
||||
|
||||
_this.confirm({ |
||||
message: _this.translate(res), |
||||
confirm: function confirm() { |
||||
if (obj.error) obj.error(res); |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} catch (err) { |
||||
// 因为 IPv6 网络问题导致的认证失败 |
||||
pendingReqNum -= 1; |
||||
} |
||||
}); |
||||
}; // 使用 Portal 页面 IP 类型认证 |
||||
|
||||
|
||||
sendAuth(); // 若符合双栈认证,则进行双栈认证 |
||||
|
||||
if (_this.portalInfo.doub) { |
||||
var _this$portalInfo3 = _this.portalInfo, |
||||
ipv4 = _this$portalInfo3.ipv4, |
||||
ipv6 = _this$portalInfo3.ipv6; // 发起另一类型认证 |
||||
|
||||
sendAuth(_this.portalInfo.nowType === 'ipv4' ? "[".concat(ipv6, "]") : ipv4); |
||||
} // 等待全部请求完成,没有 pending 中的请求则代表全部请求完成,<= 0 防止 catch 与 ajax error 方法重复 |
||||
|
||||
|
||||
var timer = setInterval(function () { |
||||
// 全部请求完成,认证成功 |
||||
if (pendingReqNum <= 0 && _this.online) { |
||||
clearInterval(timer); // 认证成功 |
||||
|
||||
if (obj.success) obj.success(successMsg); // 若未传入注销成功回调函数,则默认重定向至 index |
||||
|
||||
if (!obj.success) _this.toSuccess(); |
||||
} // 全部请求完成,认证失败 |
||||
|
||||
|
||||
if (pendingReqNum <= 0 && !_this.online) { |
||||
clearInterval(timer); |
||||
} |
||||
}, 500); // 3s 后清空 Pending Num |
||||
|
||||
setTimeout(function () { |
||||
return pendingReqNum = 0; |
||||
}, 3000); |
||||
} |
||||
``` |
||||
</details> |
||||
|
||||
|
||||
核心的是这段: |
||||
|
||||
<details> |
||||
<summary>展开查看</summary> |
||||
|
||||
```javascript |
||||
var sendAuth = function sendAuth() { |
||||
var host = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; |
||||
// 双栈认证时 IP 参数为空 |
||||
var ip = _this.portalInfo.doub && host ? '' : _this.userInfo.ip; // 获取 Token |
||||
|
||||
_classPrivateFieldGet(_assertThisInitialized(_this), _getToken).call(_assertThisInitialized(_this), host, ip, function (token) { |
||||
// 用户密码 MD5 加密 |
||||
var hmd5 = md5(password, token); // 用户信息加密 |
||||
|
||||
var i = _classPrivateFieldGet(_assertThisInitialized(_this), _encodeUserInfo).call(_assertThisInitialized(_this), { |
||||
username: username, |
||||
password: password, |
||||
ip: ip, |
||||
acid: ac_id, |
||||
enc_ver: enc |
||||
}, token); |
||||
|
||||
var str = token + username; |
||||
str += token + hmd5; |
||||
str += token + ac_id; |
||||
str += token + ip; |
||||
str += token + n; |
||||
str += token + type; |
||||
str += token + i; // 防止 IPv6 请求网络不通进行 try catch |
||||
|
||||
try { |
||||
pendingReqNum += 1; // 发起认证请求 |
||||
|
||||
_this.ajax.jsonp({ |
||||
host: host, |
||||
url: _classPrivateFieldGet(_assertThisInitialized(_this), _api).auth, |
||||
params: { |
||||
action: 'login', |
||||
username: username, |
||||
password: _this.userInfo.otp ? '{OTP}' + password : '{MD5}' + hmd5, |
||||
os: _this.portalInfo.userDevice.device, |
||||
name: _this.portalInfo.userDevice.platform, |
||||
// 未开启双栈认证,参数为 0 |
||||
// 开启双栈认证,向 Portal 当前页面 IP 认证时,参数为 1 |
||||
// 开启双栈认证,向 Portal 另外一种 IP 认证时,参数为 0 |
||||
double_stack: _this.portalInfo.doub && !host ? 1 : 0, |
||||
chksum: sha1(str), |
||||
info: i, |
||||
ac_id: ac_id, |
||||
ip: ip, |
||||
n: n, |
||||
type: type |
||||
}, |
||||
|
||||
``` |
||||
</details> |
||||
|
||||
第一个请求的格式很好理解,这里就不多分析了,咱先来看看第二个请求的代码 |
||||
|
||||
通过第二个请求的参数分析,不难发现这些字段来自这里: |
||||
```javascript |
||||
action: 'login', |
||||
username: username, |
||||
password: _this.userInfo.otp ? '{OTP}' + password : '{MD5}' + hmd5, |
||||
os: _this.portalInfo.userDevice.device, |
||||
name: _this.portalInfo.userDevice.platform, |
||||
// 未开启双栈认证,参数为 0 |
||||
// 开启双栈认证,向 Portal 当前页面 IP 认证时,参数为 1 |
||||
// 开启双栈认证,向 Portal 另外一种 IP 认证时,参数为 0 |
||||
double_stack: _this.portalInfo.doub && !host ? 1 : 0, |
||||
chksum: sha1(str), |
||||
info: i, |
||||
ac_id: ac_id, |
||||
ip: ip, |
||||
n: n, |
||||
type: type |
||||
``` |
||||
|
||||
上下文找找,这里的`action`, `os`, `name`, `n`等等都是固定的,推测的含义大概如下: |
||||
|
||||
| 字段 | 含义 | |
||||
| --- | --- | |
||||
| action | 操作,这里当然就是login | |
||||
| username | 用户名,就是学号 | |
||||
| password | 密码,在这里就很容易发现其实就是md5(密码原文+challenge) | |
||||
| os | 操作系统 | |
||||
| name | 操作系统名称,在这个js文件里边都能找到定义 | |
||||
| double_stack | 看注释就知道是否为双栈(ipv4/6)认证(但是不知道为什么没有ipv6...) | |
||||
| chksum | 参数校验,值为str字段进行sha1计算之后的值了 | |
||||
| info | 一些登录信息,具体算法比较特殊,下面再仔细讲讲 | |
||||
| ac_id | ac id | |
||||
| n | 不是很清楚,但是看上面的代码是写死固定的200 | |
||||
| type | 类型,不知道是什么的类型,看上面写的也是写死的1 | |
||||
|
||||
### info字段加密分析 |
||||
|
||||
不难看出,对i赋值的是这段代码 |
||||
```javascript |
||||
var i = _classPrivateFieldGet(_assertThisInitialized(_this), _encodeUserInfo).call(_assertThisInitialized(_this), { |
||||
username: username, |
||||
password: password, |
||||
ip: ip, |
||||
acid: ac_id, |
||||
enc_ver: enc |
||||
}, token); |
||||
``` |
||||
这些函数都只是虚晃一枪,都只是一些回调之类的操作,真正执行函数的是`_encodeUserInfo`函数,call里边异步回调的时候把一个js对象和token(也就是上面拿到的challenge码)传到了`_encodeUserInfo`函数里边。 |
||||
|
||||
那这个函数究竟在哪? |
||||
|
||||
不用太复杂的方法,直接粗暴Ctrl+F找就行了,找一找,原来是在这里:(约681行处) |
||||
|
||||
<details> |
||||
<summary>展开查看</summary> |
||||
|
||||
```javascript |
||||
_encodeUserInfo.set(_assertThisInitialized(_this), { |
||||
writable: true, |
||||
value: function value(info, token) { |
||||
// 克隆自 $.base64,防止污染 |
||||
var base64 = _this.clone($.base64); // base64 设置 Alpha |
||||
|
||||
base64.setAlpha('LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA'); // 用户信息转 JSON |
||||
|
||||
info = JSON.stringify(info); |
||||
|
||||
function encode(str, key) { |
||||
if (str === '') return ''; |
||||
var v = s(str, true); |
||||
var k = s(key, false); |
||||
if (k.length < 4) k.length = 4; |
||||
var n = v.length - 1, |
||||
z = v[n], |
||||
y = v[0], |
||||
c = 0x86014019 | 0x183639A0, |
||||
m, |
||||
e, |
||||
p, |
||||
q = Math.floor(6 + 52 / (n + 1)), |
||||
d = 0; |
||||
|
||||
while (0 < q--) { |
||||
d = d + c & (0x8CE0D9BF | 0x731F2640); |
||||
e = d >>> 2 & 3; |
||||
|
||||
for (p = 0; p < n; p++) { |
||||
y = v[p + 1]; |
||||
m = z >>> 5 ^ y << 2; |
||||
m += y >>> 3 ^ z << 4 ^ (d ^ y); |
||||
m += k[p & 3 ^ e] ^ z; |
||||
z = v[p] = v[p] + m & (0xEFB8D130 | 0x10472ECF); |
||||
} |
||||
|
||||
y = v[0]; |
||||
m = z >>> 5 ^ y << 2; |
||||
m += y >>> 3 ^ z << 4 ^ (d ^ y); |
||||
m += k[p & 3 ^ e] ^ z; |
||||
z = v[n] = v[n] + m & (0xBB390742 | 0x44C6F8BD); |
||||
} |
||||
|
||||
return l(v, false); |
||||
} |
||||
|
||||
function s(a, b) { |
||||
var c = a.length; |
||||
var v = []; |
||||
|
||||
for (var i = 0; i < c; i += 4) { |
||||
v[i >> 2] = a.charCodeAt(i) | a.charCodeAt(i + 1) << 8 | a.charCodeAt(i + 2) << 16 | a.charCodeAt(i + 3) << 24; |
||||
} |
||||
|
||||
if (b) v[v.length] = c; |
||||
return v; |
||||
} |
||||
|
||||
function l(a, b) { |
||||
var d = a.length; |
||||
var c = d - 1 << 2; |
||||
|
||||
if (b) { |
||||
var m = a[d - 1]; |
||||
if (m < c - 3 || m > c) return null; |
||||
c = m; |
||||
} |
||||
|
||||
for (var i = 0; i < d; i++) { |
||||
a[i] = String.fromCharCode(a[i] & 0xff, a[i] >>> 8 & 0xff, a[i] >>> 16 & 0xff, a[i] >>> 24 & 0xff); |
||||
} |
||||
|
||||
return b ? a.join('').substring(0, c) : a.join(''); |
||||
} |
||||
|
||||
return '{SRBX1}' + base64.encode(encode(info, token)); |
||||
} |
||||
}); |
||||
``` |
||||
</details> |
||||
|
||||
好!接下来就是改写了。 |
||||
|
||||
一般来说,如果过于复杂的话咱们通常是直接调用js执行,但是嘛...这就可能会把咱们的程序搞得非常庞大,效率也很低,但是也不是不能用是吧... |
||||
|
||||
这段看起来不是很难,那咱就开工 |
||||
|
||||
## 改造,开工! |
||||
|
||||
粗略看下来,主要就是用encode函数生成了一个字符串。再对这个字符串进行一次base64编码(当然,是被动过手脚的) |
||||
|
||||
> 就是这里被动了手脚:`base64.setAlpha('LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA');` |
||||
> |
||||
> 虽然说是动了手脚,但是具体的base64算法是一样的,只不过是字母表被换了顺序而已 |
||||
|
||||
encode函数在开头调用了两次s函数,把str和key转成了又一个神奇的数组,这里的str就是前面传进来的,转成了json字符串的js对象 |
||||
|
||||
那咱们就先改造s函数。 |
||||
|
||||
<details> |
||||
<summary>s函数</summary> |
||||
|
||||
```javascript |
||||
function s(a, b) { |
||||
var c = a.length; |
||||
var v = []; |
||||
|
||||
for (var i = 0; i < c; i += 4) { |
||||
v[i >> 2] = a.charCodeAt(i) | a.charCodeAt(i + 1) << 8 | a.charCodeAt(i + 2) << 16 | a.charCodeAt(i + 3) << 24; |
||||
} |
||||
|
||||
if (b) v[v.length] = c; |
||||
return v; |
||||
} |
||||
``` |
||||
</details> |
||||
|
||||
这个简单,就是一些普通的位移操作而已,直接照着葫芦画瓢就行了 |
||||
|
||||
咱就用go写一写: |
||||
|
||||
```go |
||||
// 神秘的“初步”加密代码,把字符串一四个字符为一组转成神秘的int32数组 |
||||
func magicEncode(source string, sizeOnLast bool) (result []int32) { |
||||
data := []int32(source) |
||||
dataLen := len(data) |
||||
|
||||
resultLen := dataLen / 4 |
||||
if sizeOnLast { |
||||
result = make([]int32, resultLen+1) |
||||
result[resultLen] = int32(dataLen) |
||||
} else { |
||||
result = make([]int32, resultLen) |
||||
} |
||||
|
||||
for i := 0; i < dataLen; i += 4 { |
||||
result[i>>2] = get(data, i, dataLen) | get(data, i+1, dataLen)<<8 | get(data, i+2, dataLen)<<16 | get(data, i+3, dataLen)<<24 |
||||
} |
||||
|
||||
return result |
||||
} |
||||
``` |
||||
|
||||
因为js里边数组越界也是不会报错的,但是go和大部分语言就不一样了,所以咱们要另外新开一个函数简单的get函数出来,数组越界了就直接返回0而不是报错 |
||||
|
||||
```go |
||||
func get(data []int32, index int, length int) int32 { |
||||
if index >= length { |
||||
return 0 |
||||
} else { |
||||
return data[index] |
||||
} |
||||
} |
||||
``` |
||||
|
||||
然后就是l函数了: |
||||
|
||||
<details> |
||||
<summary>l函数</summary> |
||||
|
||||
```javascript |
||||
function l(a, b) { |
||||
var d = a.length; |
||||
var c = d - 1 << 2; |
||||
|
||||
if (b) { |
||||
var m = a[d - 1]; |
||||
if (m < c - 3 || m > c) return null; |
||||
c = m; |
||||
} |
||||
|
||||
for (var i = 0; i < d; i++) { |
||||
a[i] = String.fromCharCode(a[i] & 0xff, a[i] >>> 8 & 0xff, a[i] >>> 16 & 0xff, a[i] >>> 24 & 0xff); |
||||
} |
||||
|
||||
return b ? a.join('').substring(0, c) : a.join(''); |
||||
} |
||||
``` |
||||
</details> |
||||
|
||||
这个也简单,跟上面的一样照着来就好了,但是有一些问题还是需要注意的 |
||||
|
||||
```go |
||||
|
||||
//和上面的是反过来的 |
||||
func magicDecode(data []int32, sizeOnLast bool) (result []int32) { |
||||
dataLength := len(data) |
||||
|
||||
c := dataLength - 1<<2 |
||||
|
||||
if sizeOnLast { |
||||
m := int(data[dataLength-1]) |
||||
if m < c-3 || m > c { |
||||
return nil |
||||
} |
||||
c = m |
||||
} |
||||
|
||||
for i := 0; i < dataLength; i++ { |
||||
result = append(result, data[i]&0xff, uRightShift(data[i], 8)&0xff, uRightShift(data[i], 16)&0xff, uRightShift(data[i], 24)&0xff) |
||||
} |
||||
|
||||
if sizeOnLast { |
||||
return append(result, int32(c)) |
||||
} else { |
||||
return result |
||||
} |
||||
} |
||||
``` |
||||
|
||||
可以看到,这里又有一个新的函数`uRightShift` |
||||
|
||||
```go |
||||
// 在go中实现无符号右移(>>>) |
||||
func uRightShift(number int32, shift int) int32 { |
||||
return int32(uint32(number) >> shift) |
||||
} |
||||
``` |
||||
|
||||
其实就是无符号右移>>>在go中的实现 |
||||
|
||||
接下来就是重头戏`encode`函数了: |
||||
|
||||
<details> |
||||
<summary>encode函数</summary> |
||||
|
||||
```javascript |
||||
function encode(str, key) { |
||||
if (str === '') return ''; |
||||
var v = s(str, true); |
||||
var k = s(key, false); |
||||
if (k.length < 4) k.length = 4; |
||||
var n = v.length - 1, |
||||
z = v[n], |
||||
y = v[0], |
||||
c = 0x86014019 | 0x183639A0, |
||||
m, |
||||
e, |
||||
p, |
||||
q = Math.floor(6 + 52 / (n + 1)), |
||||
d = 0; |
||||
|
||||
while (0 < q--) { |
||||
d = d + c & (0x8CE0D9BF | 0x731F2640); |
||||
e = d >>> 2 & 3; |
||||
|
||||
for (p = 0; p < n; p++) { |
||||
y = v[p + 1]; |
||||
m = z >>> 5 ^ y << 2; |
||||
m += y >>> 3 ^ z << 4 ^ (d ^ y); |
||||
m += k[p & 3 ^ e] ^ z; |
||||
z = v[p] = v[p] + m & (0xEFB8D130 | 0x10472ECF); |
||||
} |
||||
|
||||
y = v[0]; |
||||
m = z >>> 5 ^ y << 2; |
||||
m += y >>> 3 ^ z << 4 ^ (d ^ y); |
||||
m += k[p & 3 ^ e] ^ z; |
||||
z = v[n] = v[n] + m & (0xBB390742 | 0x44C6F8BD); |
||||
} |
||||
|
||||
return l(v, false); |
||||
} |
||||
``` |
||||
</details> |
||||
|
||||
改写成Go之后是这个样子的,一些常数在这里就直接算出来了: |
||||
|
||||
```go |
||||
// encode 直接照着原网页的js代码改的,只是能跑 |
||||
// 因为各种类型转换从理论上来说性能可能会差了点,但是在这里的使用情景几乎是感觉不到的 |
||||
// 难度其实主要还是在于动态类型语言和静态类型语言在运算时数据溢出的问题不好处理 |
||||
func encode(str string, key string) []int32 { |
||||
v := magicEncode(str, true) |
||||
k := magicEncode(key, false) |
||||
|
||||
if len(k) < 4 { |
||||
for i := 0; i < (4 - len(k)); i++ { |
||||
k = append(k, 0) |
||||
} |
||||
} |
||||
|
||||
n := int32(len(v) - 1) |
||||
z := v[n] |
||||
y := v[0] |
||||
c := int32(-1640531527) |
||||
q := int(math.Floor(float64(6+52/(n+1)))) - 1 |
||||
d := int32(0) |
||||
var e, p int32 |
||||
|
||||
// 这里的m用int64(long)是因为后面+=的时候int32会溢出 |
||||
// 其他变量不用int64是因为直接用int64会导致位运算错误,和js的运算结果不一致 |
||||
var m int64 |
||||
|
||||
for ; q >= 0; q-- { |
||||
d = d + c&(-1) |
||||
e = uRightShift(d, 2) & 3 |
||||
|
||||
for p = 0; p < n; p++ { |
||||
y = v[p+1] |
||||
m = int64(uRightShift(z, 5) ^ y<<2) |
||||
m += int64(uRightShift(y, 3) ^ z<<4 ^ (d ^ y)) |
||||
m += int64(k[p&3^e] ^ z) |
||||
v[p] = v[p] + int32(m&(-1)) |
||||
z = v[p] |
||||
} |
||||
|
||||
y = v[0] |
||||
m = int64(uRightShift(z, 5) ^ y<<2) |
||||
m += int64(uRightShift(y, 3) ^ z<<4 ^ (d ^ y)) |
||||
m += int64(k[p&3^e] ^ z) |
||||
v[n] = v[n] + int32(m&(-1)) |
||||
z = v[n] |
||||
} |
||||
|
||||
return magicDecode(v, false) |
||||
} |
||||
``` |
||||
|
||||
看着挺吓人,但其实还好,照着思路抄下来就好了,最主要的还是强弱类型语言的不同地方要注意一下 |
||||
|
||||
最后,这个请求加密就改写好了。完整的代码已开源在Github上:[lensferno/canti](https://github.com/lensferno/canti) |
||||
|
||||
具体的加密部分的文件在[app/codecs/encode.go](https://github.com/lensferno/canti/blob/main/app/codecs/encode.go)中 |
||||
|
||||
就这样啦 |
||||
|
||||
> 咚咚咚折腾一番,发现其实之前就有人研究过了:[校园网模拟登录 | xia0ji233's blog](https://xia0ji233.pro/2021/12/08/%E6%A0%A1%E5%9B%AD%E7%BD%91%E6%A8%A1%E6%8B%9F%E7%99%BB%E5%BD%95/) 等等,这个只是其中一个 |
||||
> |
||||
> 这里也有另外一个大佬写的go版本,算法可能会更好,这里的只是照着js改而已 |
||||
> |
||||
> [Debuffxb/srun-go - main.go](https://github.com/Debuffxb/srun-go/blob/master/main.go) |
@ -0,0 +1,46 @@ |
||||
title: 奇怪的事... |
||||
author: lenfrex |
||||
cover: |
||||
tags: |
||||
- 日常 |
||||
- 玄学 |
||||
categories: |
||||
- 折腾 |
||||
- 日常 |
||||
date: 2022-03-08 08:54:00 |
||||
--- |
||||
今天用rust编译一个库,因为之前没用过rust,是新装上去的。 |
||||
|
||||
我这里的linker指定的是mingw,也就是gcc那一套。 |
||||
|
||||
按照文档的说法,`cargo build --release`就好了。 |
||||
|
||||
编译的过程中,一开始还很顺利,但是半路就报错: |
||||
|
||||
``` |
||||
error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1 |
||||
| |
||||
= note: "x86_64-w64-mingw32-gcc" "-fno-use-linker-plugin" "-Wl,--dynamicbase" "-Wl,--disable-auto-image-base" "-m6... |
||||
|
||||
// 有很多路径的输出... |
||||
|
||||
= note: collect2.exe: fatal error: CreateProcess: No such file or directory |
||||
compilation terminated. |
||||
|
||||
|
||||
error: could not compile `crc32fast` due to previous error |
||||
warning: build failed, waiting for other jobs to finish... |
||||
error: build failed |
||||
``` |
||||
|
||||
行吧,老办法,搜。 |
||||
|
||||
好嘛,玄学问题... |
||||
|
||||
原来的系统变量PATH里边mingw的路径为`D:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin` |
||||
|
||||
你猜怎么着? |
||||
|
||||
"bin"后边多加个斜杠就好了 ←_← |
||||
|
||||
附:其他人的方法 [gcc: error: CreateProcess: No such file or directory解决方案_肥宅Sean-CSDN博客](https://blog.csdn.net/a19990412/article/details/78360518) |
@ -0,0 +1,97 @@ |
||||
title: Tag Plugins |
||||
date: 2013-12-25 00:14:39 |
||||
tags: |
||||
--- |
||||
|
||||
This post is used for testing tag plugins. See [docs](http://zespia.tw/hexo/docs/tag-plugins.html) for more info. |
||||
|
||||
## Block Quote |
||||
|
||||
### Normal blockquote |
||||
|
||||
> Praesent diam elit, interdum ut pulvinar placerat, imperdiet at magna. |
||||
|
||||
### Quote from a book |
||||
|
||||
{% blockquote David Levithan, Wide Awake %} |
||||
Do not just seek happiness for yourself. Seek happiness for all. Through kindness. Through mercy. |
||||
{% endblockquote %} |
||||
|
||||
### Quote from Twitter |
||||
|
||||
{% blockquote @DevDocs https://twitter.com/devdocs/status/356095192085962752 %} |
||||
NEW: DevDocs now comes with syntax highlighting. http://devdocs.io |
||||
{% endblockquote %} |
||||
|
||||
### Quote from an article on the web |
||||
|
||||
{% blockquote Seth Godin http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html Welcome to Island Marketing %} |
||||
Every interaction is both precious and an opportunity to delight. |
||||
{% endblockquote %} |
||||
|
||||
## Code Block |
||||
|
||||
### Normal code block |
||||
|
||||
``` |
||||
alert('Hello World!'); |
||||
``` |
||||
|
||||
### With caption |
||||
|
||||
{% codeblock Array.map %} |
||||
array.map(callback[, thisArg]) |
||||
{% endcodeblock %} |
||||
|
||||
### With caption and URL |
||||
|
||||
{% codeblock .compact http://underscorejs.org/#compact Underscore.js %} |
||||
.compact([0, 1, false, 2, ‘’, 3]); |
||||
=> [1, 2, 3] |
||||
{% endcodeblock %} |
||||
|
||||
### With marked lines |
||||
|
||||
Line 1,7-8,10 should be marked with different color. |
||||
|
||||
{% codeblock lang:js mark:1,7-8,10 %} |
||||
const http = require('http'); |
||||
|
||||
const hostname = '127.0.0.1'; |
||||
const port = 1337; |
||||
|
||||
http.createServer((req, res) => { |
||||
res.writeHead(200, { 'Content-Type': 'text/plain' }); |
||||
res.end('Hello World\n'); |
||||
}).listen(port, hostname, () => { |
||||
console.log(`Server running at http://${hostname}:${port}/`); |
||||
}); |
||||
{% endcodeblock %} |
||||
|
||||
Note: Theme's style should support `.highlight.line.marked` (recommend to use the selection or current line color). |
||||
|
||||
### Gist |
||||
|
||||
{% gist 996818 %} |
||||
|
||||
### jsFiddle |
||||
|
||||
{% jsfiddle ccWP7 %} |
||||
|
||||
## Pullquote |
||||
|
||||
### Left |
||||
|
||||
{% pullquote left %} |
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. |
||||
{% endpullquote %} |
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempus molestie arcu, et fringilla mauris placerat ac. Nullam luctus bibendum risus. Ut cursus sed ipsum feugiat egestas. Suspendisse elementum, velit eu consequat consequat, augue lorem dapibus libero, eget pulvinar dolor est sit amet nulla. Suspendisse a porta tortor, et posuere mi. Pellentesque ultricies, mi quis volutpat malesuada, erat felis vulputate nisl, ac congue ante tortor ut ante. Proin aliquam sem vel mauris tincidunt, eget scelerisque tortor euismod. Nulla tincidunt enim nec commodo dictum. Mauris id sapien et orci gravida luctus id ut dui. In vel vulputate odio. Duis vel turpis molestie, scelerisque enim eu, lobortis eros. Cras at ipsum gravida, sagittis ante vel, viverra tellus. Nunc mauris turpis, elementum ullamcorper nisl pretium, ultrices cursus justo. Mauris porttitor commodo eros, ac ornare orci interdum in. Cras fermentum cursus leo sed mattis. In dignissim lorem sem, sit amet elementum mauris venenatis ac. |
||||
|
||||
### Right |
||||
|
||||
{% pullquote right %} |
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. |
||||
{% endpullquote %} |
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ligula justo, lobortis sit amet semper vel, dignissim sit amet libero. Praesent ac tempus ligula. Maecenas at gravida odio. Etiam tristique volutpat lacus eu faucibus. Donec non tempus arcu. Phasellus adipiscing, mauris nec mollis egestas, ipsum nunc auctor velit, et rhoncus lorem ipsum at ante. Praesent et sem in velit volutpat auctor. Duis vel mauris nulla. Maecenas mattis interdum ante, quis sagittis nibh cursus et. Nulla facilisi. Morbi convallis gravida tortor, ut fermentum enim gravida et. Nunc vel dictum nisl, non ultrices libero. Proin vestibulum felis eget orci consectetur lobortis. Vestibulum augue nulla, iaculis vitae augue vehicula, dignissim ultrices libero. Sed imperdiet urna et quam ultrices tincidunt nec ac magna. Etiam vel pharetra elit. |
@ -0,0 +1,9 @@ |
||||
title: Tags |
||||
date: 2013-12-24 23:29:53 |
||||
tags: |
||||
- Foo |
||||
- Bar |
||||
- Baz |
||||
--- |
||||
|
||||
This post contains 3 tags. Make sure your theme can display all of the tags. |
@ -0,0 +1,11 @@ |
||||
title: ubuntu磁盘扩容小记录 |
||||
author: lensfrex |
||||
date: 2022-07-28 09:36:44 |
||||
tags: |
||||
--- |
||||
扩容 lventend -L (+/-)21T /dev/mapper/ubuntu--vg-ubuntu--lv |
||||
lvresize -l +100FREE /dev..... 按百分比扩容 |
||||
|
||||
使扩容生效 resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv |
||||
|
||||
2fs:to file system |
@ -0,0 +1,14 @@ |
||||
title: Videos |
||||
date: 2013-12-25 00:19:15 |
||||
tags: |
||||
--- |
||||
|
||||
This is a video test post. |
||||
|
||||
**Youtube** |
||||
|
||||
{% youtube TIbZDRXM-Tg %} |
||||
|
||||
**Vimeo** |
||||
|
||||
{% vimeo 82090131 %} |
@ -0,0 +1,18 @@ |
||||
title: 中文測試 |
||||
date: 2013-12-24 23:31:30 |
||||
tags: |
||||
--- |
||||
|
||||
This is a Chinese test post. |
||||
|
||||
善我王上魚、產生資西員合兒臉趣論。畫衣生這著爸毛親可時,安程幾?合學作。觀經而作建。都非子作這!法如言子你關!手師也。 |
||||
|
||||
以也座論頭室業放。要車時地變此親不老高小是統習直麼調未,行年香一? |
||||
|
||||
就竟在,是我童示讓利分和異種百路關母信過明驗有個歷洋中前合著區亮風值新底車有正結,進快保的行戰從:弟除文辦條國備當來際年每小腳識世可的的外的廣下歌洲保輪市果底天影;全氣具些回童但倒影發狀在示,數上學大法很,如要我……月品大供這起服滿老?應學傳者國:山式排只不之然清同關;細車是!停屋常間又,資畫領生,相們制在?公別的人寫教資夠。資再我我!只臉夫藝量不路政吃息緊回力之;兒足灣電空時局我怎初安。意今一子區首者微陸現際安除發連由子由而走學體區園我車當會,經時取頭,嚴了新科同?很夫營動通打,出和導一樂,查旅他。坐是收外子發物北看蘭戰坐車身做可來。道就學務。 |
||||
|
||||
國新故。 |
||||
|
||||
> 工步他始能詩的,裝進分星海演意學值例道……於財型目古香亮自和這乎?化經溫詩。只賽嚴大一主價世哥受的沒有中年即病行金拉麼河。主小路了種就小為廣不? |
||||
|
||||
*From [亂數假文產生器 - Chinese Lorem Ipsum](http://www.richyli.com/tool/loremipsum/)* |
@ -0,0 +1,20 @@ |
||||
title: 日本語テスト |
||||
date: 2013-12-24 23:33:26 |
||||
tags: |
||||
--- |
||||
|
||||
This is a Japanese test post. |
||||
|
||||
私は昨日ついにその助力家というのの上よりするたなけれ。 |
||||
|
||||
最も今をお話団はちょうどこの前後なかろでくらいに困りがいるたをは帰着考えたなかって、そうにもするでうたらない。 |
||||
|
||||
がたを知っないはずも同時に九月をいよいよたありた。 |
||||
|
||||
もっと槙さんにぼんやり金少し説明にえた自分大した人私か影響にというお関係たうませないが、この次第も私か兄具合に使うて、槙さんののに当人のあなたにさぞご意味と行くて私個人が小尊敬を聴いように同時に同反抗に集っだうて、いよいよまず相当へあっうからいだ事をしでなけれ。 |
||||
|
||||
> それでそれでもご時日をしはずはたったいやと突き抜けるますて、その元がは行ったてという獄を尽すていけですた。 |
||||
|
||||
この中道具の日その学校はあなたごろがすまなりかとネルソンさんの考えるですん、辺の事実ないというご盲従ありたですと、爺さんのためが薬缶が結果までの箸の当時してならて、多少の十月にためからそういう上からとにかくしましないと触れべきものたで、ないうですと多少お人達したのでたた。 |
||||
|
||||
*From [すぐ使えるダミーテキスト - 日本語 Lorem ipsum](https://lipsum.sugutsukaeru.jp/index.cgi)* |
@ -0,0 +1,54 @@ |
||||
title: 装个Qt... |
||||
author: lenfrex |
||||
cover: https://oss-img.ciduid.top/blog/covers/cover17.jpg |
||||
tags: |
||||
- 闲聊 |
||||
- 折腾 |
||||
categories: |
||||
- 闲聊 |
||||
- 折腾 |
||||
date: 2022-03-06 09:41:00 |
||||
--- |
||||
*自从qt新版本安装方式取消了离线安装之后,安装的过程变得麻烦了很多... |
||||
下载要下个半天...以前qt5的时候可以直接在国内镜像站下离线包,直接就可以跑满带宽,但现在qt的在线安装器经常会出现网络错误,甚至登陆不上去...* |
||||
|
||||
*今天去装个qt6/5,挂着代理,但是这安装器偏偏不走代理,只好用proxifier给他调教一下了* |
||||
|
||||
--- |
||||
|
||||
// update0:啥?不知道不知道,反正不是我眼瞎 |
||||
![](https://oss-img.ciduid.top/aether/Snipaste_2022-03-08_13-00-13.png) |
||||
|
||||
--- |
||||
|
||||
#### 下载安装器 |
||||
|
||||
这次就不去qt的官网大门下载了,真的太烦,注册登录订阅一大堆信息要填,咱们就直接走后门 --> [Index of /official_releases/online_installers (qt.io)](https://download.qt.io/official_releases/online_installers/) |
||||
|
||||
这就有可以直接下载的qt installer |
||||
|
||||
--- |
||||
|
||||
#### proxifer调教 |
||||
|
||||
installer下下来了,但是在登录的时候卡住了,看看代理,没有一点qt相关的记录,估计这玩意一股脑的走直连了。这下可就要请出proxifer了。 |
||||
|
||||
在proxifier里边,设置好代理服务器和代理规则(具体的以后再讲吧 *~~鸽+1~~*),去qt instraller里边登录,当看到有qt installer的记录的时候就表明installer已经成功走上代理了。 |
||||
|
||||
去代理程序那里瞧瞧,也有qt网址的访问记录了,登录什么的也一番丰顺了。 |
||||
|
||||
--- |
||||
|
||||
说来也奇怪,等到installer正式开始安装的时候,发现代理程序里边qt居然就走镜像站了...好家伙... |
||||
|
||||
分析log,文件信息是去找qt.io,真正下载却是去上交大的镜像站下载...之前就是一直卡在qtio这里... |
||||
|
||||
![图片不见了哟](https://oss-img.ciduid.top/aether/Snipaste_2022-03-06_10-18-27.png) |
||||
|
||||
等下载,去网上冲浪找找资料,发现貌似可以直接对installer设定启动参数 `--mirror`指定镜像,例如`--mirror https://mirrors.sjtug.sjtu.edu.cn/qt` |
||||
|
||||
但是仔细想想,qt.io这关还是过不去,只是指定了下载的镜像,对于文件信息和登录认证并没有什么用,所以这里只能说是能保证让他下大文件的时候从镜像站下不从主站下,对于代理流量少的人来说确实有些用。 |
||||
|
||||
我下的是qt5.12 + qt6.2,一共4个多g,大概要下半个小时,这个时候就可以去干点别的事了*~~(比如我现在在写这篇文章...)~~* |
||||
|
||||
不得不说,自从qt对商用/非商用许可变紧之后安装的过程就变得十分的麻烦了... |
@ -0,0 +1,5 @@ |
||||
title: Page |
||||
date: 2013-12-26 22:52:56 |
||||
--- |
||||
|
||||
This is a page test. |
Loading…
Reference in new issue